Compare commits

..

No commits in common. "master" and "3.0.0" have entirely different histories.

78 changed files with 2531 additions and 4939 deletions

View File

@ -1,37 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
Application log file recorded during an error.
To start recording a log file, open the settings window,
enable the 'write trace file' option and apply the settings.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. windows 10, ubuntu 18.04]
- Desktop environment (linux only): [e.g. KDE, Gnome]
- App version (release archive name): [e.g. 3.0.0-win32, 3.0.0-compatible-win64]
**Additional context**
Add any other context about the problem here.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,4 +1,4 @@
name: App build name: Build
on: [push] on: [push]
@ -6,7 +6,7 @@ jobs:
release: release:
name: Create release name: Create release
if: contains(github.ref, '/tags/') if: contains(github.ref, '/tags/')
runs-on: ubuntu-18.04 runs-on: ubuntu-16.04
steps: steps:
- name: Create release - name: Create release
id: create_release id: create_release
@ -29,17 +29,17 @@ jobs:
name: release_upload_url name: release_upload_url
build: build:
name: Build ${{ matrix.config.name }}${{ matrix.config.tag }} name: Build ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }} runs-on: ${{ matrix.config.os }}
env: env:
OS: ${{ matrix.config.name }} OS: ${{ matrix.config.name }}
MSVC_VERSION: C:/Program Files/Microsoft Visual Studio/2022/Enterprise MSVC_VERSION: 2019/Enterprise
strategy: strategy:
matrix: matrix:
config: config:
- { name: "win64", os: windows-latest } - { name: "win64", os: windows-latest }
- { name: "win32", os: windows-latest } - { name: "win32", os: windows-latest }
- { name: "linux", os: ubuntu-18.04 } - { name: "linux", os: ubuntu-16.04 }
# - { name: "macos", os: macos-latest } # - { name: "macos", os: macos-latest }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -54,20 +54,56 @@ jobs:
- name: Install system libs - name: Install system libs
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libxcb-* sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libxcb-util-dev
echo "QMAKE_FLAGS=QMAKE_CXX=g++-10 QMAKE_CC=gcc-10 QMAKE_LINK=g++-10" >> $GITHUB_ENV echo ::set-env name=QMAKE_FLAGS::QMAKE_CXX=g++-9 QMAKE_CC=gcc-9
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v2 uses: actions/cache@v1
with: with:
path: deps path: deps
key: ${{ env.OS }}-${{ hashFiles('./share/ci/*.py') }} key: ${{ env.OS }}-deps
- name: Make a release - name: Get Qt
run: python ./share/ci/get_qt.py
- name: Get ssl
run: python ./share/ci/get_qt_ssl.py
- name: Get leptonica
run: python ./share/ci/get_leptonica.py
- name: Get tesseract
run: python ./share/ci/get_tesseract.py
- name: Get hunspell
run: python ./share/ci/get_hunspell.py
- name: Test
run: python ./share/ci/test.py
- name: Build
run: python ./share/ci/build.py
- name: Create AppImage
if: runner.os == 'Linux'
shell: bash shell: bash
run: | run: |
python ./share/ci/release.py python ./share/ci/appimage.py
echo "artifact=`python ./share/ci/release.py artifact_name`" >> $GITHUB_ENV echo ::set-env name=artifact::`python ./share/ci/appimage.py artifact_name`
- name: Create win deploy
if: runner.os == 'Windows'
shell: bash
run: |
python ./share/ci/windeploy.py
echo ::set-env name=artifact::`python ./share/ci/windeploy.py artifact_name`
- name: Create mac deploy
if: runner.os == 'macOS'
shell: bash
run: |
python ./share/ci/macdeploy.py
echo ::set-env name=artifact::`python ./share/ci/macdeploy.py artifact_name`
- name: Upload build artifact - name: Upload build artifact
if: env.artifact != '' if: env.artifact != ''
@ -78,7 +114,7 @@ jobs:
- name: Download release url - name: Download release url
if: contains(github.ref, '/tags/') if: contains(github.ref, '/tags/')
uses: actions/download-artifact@v4.1.7 uses: actions/download-artifact@v1
with: with:
name: release_upload_url name: release_upload_url
path: ./ path: ./
@ -86,7 +122,7 @@ jobs:
- name: Set release env - name: Set release env
if: contains(github.ref, '/tags/') if: contains(github.ref, '/tags/')
shell: bash shell: bash
run: echo "upload_url=`cat ./release_upload_url`" >> $GITHUB_ENV run: echo ::set-env name=upload_url::`cat ./release_upload_url`
- name: Upload release artifacts - name: Upload release artifacts
if: contains(github.ref, '/tags/') if: contains(github.ref, '/tags/')
@ -98,3 +134,13 @@ jobs:
asset_path: ./${{ env.artifact }} asset_path: ./${{ env.artifact }}
asset_name: ${{ env.artifact }} asset_name: ${{ env.artifact }}
asset_content_type: application/zip asset_content_type: application/zip
- name: Upload sourceforge
if: contains(github.ref, '/tags/')
env:
SF_PKEY: ${{ secrets.SF_PKEY }}
SF_API: ${{ secrets.SF_API }}
run: |
python -m pip install --upgrade pip
pip install paramiko
python ./share/ci/sourceforge.py ./${{ env.artifact }}

View File

@ -1,53 +1,18 @@
# Screen Translator # Screen Translator
**The project is almost abandoned. I don't have time for it and I can only fix minor issues**
## Introduction ## Introduction
This software allows you to translate any text on screen. This software allows you to translate any text on screen.
Basically it is a combination of screen capture, OCR and translation tools. Basically it is a combination of screen capture, OCR and translation tools.
Translation is currently done via online services.
## Installation ## Installation
**Windows**: download archive from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases) page, extract it and run `.exe` file. **Windows**: download archive from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases) page, extract it and run `.exe` file.
If the app fails to start complaining about missing dlls or there are any update errors related to SSL/TLS then install or repair `vs_redist*.exe` from the release archive.
**Linux**: download `.AppImage` file from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases), make executable (`chmod +x <file>`) and run it. **Linux**: download `.AppImage` file from [github releases](https://github.com/OneMoreGres/ScreenTranslator/releases), make executable (`chmod +x <file>`) and run it.
**OS X**: currently not supported. **OS X**: currently not supported.
### App translation
To install Hebrew translation of the app (thanks to [Y-PLONI](https://github.com/Y-PLONI)),
download [this](https://github.com/OneMoreGres/ScreenTranslator/releases/download/3.3.0/screentranslator_he.qm)
file and place it into the `translations` folder next to `screen-translator.exe`.
## Setup
The app doesn't have a main window.
After start it shows only the tray icon.
If the app detects invalid settings, it will show the error message via system tray.
It will also highlight the section name in red on the left panel of the settings window.
Clicking on that section name will show a more detailed error message in the right panel (also in red).
The packages downloaded from this site do not include resources, such as recognition language packs or scripts to interact with online translation services.
To download them, open the settings window and go to the `Update` section.
In the right panel, expand the `recognizers` and `translators` sections.
Select preferred items, then right click and choose `Install/Update`.
After the progress bar reaches `100%`, the resource's state will change to `Up to Date`.
You must download at least one `recognizers` resource and one `translators` resource.
After finishing downloads, go to the `Recognition` section and update the default recognition language setting (the source to be translated).
Then go to the `Translation` section, update the default translation language setting (the language to be translated into) and enable some or all translation sevices (you may also change their order by dragging).
After that all sections in the left panel should be black.
Then click `Ok` to close settings.
## Usage ## Usage
1. Run program (note that it doesn't have main window). 1. Run program (note that it doesn't have main window).
@ -56,16 +21,6 @@ Then click `Ok` to close settings.
4. Get translation of recognized text. 4. Get translation of recognized text.
5. Check for updates if something is not working. 5. Check for updates if something is not working.
## FAQ
By default resources are downloaded to the one of the user's folders.
If `Portable` setting in `General` section is checked, then resources will be downloaded to the app's folder.
Set `QTWEBENGINE_DISABLE_SANDBOX=1` environment variable when fail to start due to crash.
Answers to some frequently asked questions can be found in issues or
[wiki](https://github.com/OneMoreGres/ScreenTranslator/wiki/FAQ)
## Limitations ## Limitations
* Can not capture some dynamic web-pages/full screen applications * Can not capture some dynamic web-pages/full screen applications
@ -77,13 +32,6 @@ Answers to some frequently asked questions can be found in issues or
* see [Leptonica](https://leptonica.com/) * see [Leptonica](https://leptonica.com/)
* several online translation services * several online translation services
## Build from source
Look at the scripts (python3) in the `share/ci` folder.
Normally, you should only edit the `config.py` file.
Build dependencies at first, then build the app.
## Attributions ## Attributions
* icons made by * icons made by

View File

@ -11,6 +11,5 @@
</qresource> </qresource>
<qresource prefix="/translations"> <qresource prefix="/translations">
<file alias="screentranslator_ru.qm">share/translations/screentranslator_ru.qm</file> <file alias="screentranslator_ru.qm">share/translations/screentranslator_ru.qm</file>
<file alias="screentranslator_he.qm">share/translations/screentranslator_he.qm</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -8,7 +8,7 @@ DEPS_DIR=$$(ST_DEPS_DIR)
isEmpty(DEPS_DIR):DEPS_DIR=$$PWD/../deps isEmpty(DEPS_DIR):DEPS_DIR=$$PWD/../deps
INCLUDEPATH += $$DEPS_DIR/include INCLUDEPATH += $$DEPS_DIR/include
LIBS += -L$$DEPS_DIR/lib LIBS += -L$$DEPS_DIR/lib
LIBS += -lhunspell -lleptonica -ltesseract LIBS += -ltesseract -lleptonica -lhunspell
win32{ win32{
LIBS += -lUser32 LIBS += -lUser32
@ -21,7 +21,7 @@ linux{
SOURCES += $$PWD/external/miniz/miniz.c SOURCES += $$PWD/external/miniz/miniz.c
INCLUDEPATH += $$PWD/external INCLUDEPATH += $$PWD/external
VER=3.3.0 VER=3.0.0
DEFINES += VERSION="$$VER" DEFINES += VERSION="$$VER"
VERSION = $$VER.0 VERSION = $$VER.0
QMAKE_TARGET_COMPANY = Gres QMAKE_TARGET_COMPANY = Gres
@ -53,14 +53,12 @@ HEADERS += \
src/service/debug.h \ src/service/debug.h \
src/service/geometryutils.h \ src/service/geometryutils.h \
src/service/globalaction.h \ src/service/globalaction.h \
src/service/keysequenceedit.h \
src/service/runatsystemstart.h \ src/service/runatsystemstart.h \
src/service/singleapplication.h \ src/service/singleapplication.h \
src/service/updates.h \ src/service/updates.h \
src/service/widgetstate.h \ src/service/widgetstate.h \
src/settings.h \ src/settings.h \
src/settingseditor.h \ src/settingseditor.h \
src/settingsvalidator.h \
src/stfwd.h \ src/stfwd.h \
src/substitutionstable.h \ src/substitutionstable.h \
src/task.h \ src/task.h \
@ -91,14 +89,12 @@ SOURCES += \
src/service/debug.cpp \ src/service/debug.cpp \
src/service/geometryutils.cpp \ src/service/geometryutils.cpp \
src/service/globalaction.cpp \ src/service/globalaction.cpp \
src/service/keysequenceedit.cpp \
src/service/runatsystemstart.cpp \ src/service/runatsystemstart.cpp \
src/service/singleapplication.cpp \ src/service/singleapplication.cpp \
src/service/updates.cpp \ src/service/updates.cpp \
src/service/widgetstate.cpp \ src/service/widgetstate.cpp \
src/settings.cpp \ src/settings.cpp \
src/settingseditor.cpp \ src/settingseditor.cpp \
src/settingsvalidator.cpp \
src/substitutionstable.cpp \ src/substitutionstable.cpp \
src/translate/translator.cpp \ src/translate/translator.cpp \
src/translate/webpage.cpp \ src/translate/webpage.cpp \
@ -117,8 +113,7 @@ OTHER_FILES += \
updates.json updates.json
TRANSLATIONS += \ TRANSLATIONS += \
share/translations/screentranslator_ru.ts \ share/translations/screentranslator_ru.ts
share/translations/screentranslator_he.ts
linux { linux {
PREFIX = /usr PREFIX = /usr

View File

@ -1,125 +0,0 @@
# Changes
## 3.3.0
* Use single tesseract library (not optimized and compatible versions)
* Improved recognition
## 3.2.3
* Fixed translators order persistance
* Fixed multi-monitor support
* Improves result representation near monitor borders
* Updated recognition library
## 3.2.2
* Disabled hotkeys with several consecutive combinations
* Added the ability to use some service buttons for hotkeys
* Fixed multiple monitors support if the main one is not at the top left
* Automatic selection of the supported version of tesseract
## 3.2.1
* Fixed incorrect update install
## 3.2.0
* Improved vertical text recognition
* Improved incorrect settings notification
* Improved update process
## 3.1.2
* Fixed manually corrected text translation
## 3.1.1
* Fixed portable mode detection
## 3.1.0
* Added ability to choose a recognition version from the program
* Added some error messages about misconfiguration
* Fixed some errors
## 3.0.1
* Fixed some errors
* Added `compatible` version (fixes crash during recognition)
## 3.0.0
* Changed distribution model: a zip archive instead of an installer
* Required resources can now be downloaded from the program
* Added ability to capture images in saved areas (reuse selection)
* Many interface changes
* Updated libraries versions
## 2.0.2
* Added force translator rotation option.
## 2.0.1
* Fixed installer.
## 2.0.0
* Added a version for linux.
* Added support for multiple monitors.
* Added ability of recognition without translation.
* Added ability to recapture from old image.
* Added ability to recapture without closing capture window.
* Added ability to re-recognize other language.
* Added ability to display intermediate result when error occured.
* Added support for different translation services.
* Added ability to copy image to clipboard.
* Added ability to edit recognized text.
* Added ability to automatically correct common recognition mistakes.
* Added ability to use a proxy.
* Added ability to swap translation and recognition languages.
* Updated icons.
* Show progress on icon.
* Added ability to automatically update.
## 1.2.3
* Fixed possible crash.
* Added version information and some error messages.
## 1.2.2
* Added alternative translation source.
## 1.2.1
* Fixed the bug with the lack of translation.
* Fixed the bug with the use of language recognition by default when you select another one in OCR region selection mode.
## 1.2.0
* Changed installer.
* Added all available languages for recognition.
* Added ability to specify language when selecting the field of recognition using right click.
* Human readable language names.
* Reduced memory usage.
* Updated libraries.
## 1.1.3
* Added library libgcc_s_dw2-1.dll.
* Updated libraries.
## 1.1.2
* If you specify in the settings the path to tessdata characters "\" or "/" at the end of the path are no longer required.
## 1.1.1
* Fixed an issue with incorrect window size when display results.
## 1.1.0
* Displays the result in the window, along with the picture.
* Context menu expanded. Added buttons display the last result and copy it to the clipboard.

View File

@ -1,125 +0,0 @@
# Изменения
## 3.3.0
* Использование единой библиотеки распознавания (без оптимизированной и совместимой версий)
* Улучшено распознавание
## 3.2.3
* Исправлено сохранение порядка переводчиков в настройках
* Исправлена работа с несколькими мониторами
* Улучшено отображение результата на границе монитора
* Обновлена версия библиотеки распознавания
## 3.2.2
* Исключено задание горячих клавиш из нескольких последовательных комбинаций
* Добавлена возможность использования некоторых служебных кнопок для горячих клавиш
* Исправлена работа с несколькими мониторами, если главный находится не слева-вверху
* Автоматический выбор поддерживаемой версии tesseract
## 3.2.1
* Исправлена некорректная установка обновления
## 3.2.0
* Улучшено распознавание вертикального текста
* Улучшено информирование о некорректных настройках
* Упрощена работа с обновлениями
## 3.1.2
* Исправлен перевод исправленного вручную текста
## 3.1.1
* Исправлено определение работы в Portable режиме
## 3.1.0
* Добавлена возможность выбора версии библиотеки распознавания из программы
* Добавлены сообщения об ошибках при неправильной настройке
* Исправлены некоторые ошибки
## 3.0.1
* Исправлены некоторые ошибки
* Добавлена `совместимая` версия (исправляет падение при распознавании)
## 3.0.0
* Изменен порядок распространения: удалены установщики. Для установки достаточно распаковать папку нужного дистрибутива в желаемое место и запустить программу
* Необходимые ресурсы скачиваются из программы, а не вручную или через установщик
* Добавлена возможность захвата изображений в заранее подготовленных областях
* Много мелких изменений в интерфейсе
* Обновлены версии библиотек
## 2.0.2
* Добавлена настройка принудительной смены переводчиков.
## 2.0.1
* Исправлен установщик.
## 2.0.0
* Добавлена версия под linux.
* Добавлена поддержка нескольких мониторов.
* Добавлена возможность распознание без перевода.
* Добавлена возможность вызова старого рисунка для выделения.
* Добавлена возможность повторного выделения без закрытия окна захвата.
* Добавлена возможность повторного распознания на другом языке.
* Добавлена возможность отображения промежуточного результата при ошибке перевода.
* Добавлена поддержка разных сервисов перевода.
* Добавлена возможность копирования изображения в буфер.
* Добавлена возможность редакции распознанного текста.
* Добавлена возможность автоматической коррекции частых ошибок распознавания.
* Добавлена возможность использования прокси.
* Добавлена возможность разовой смена языка перевода и распознавания.
* Обновлены иконки.
* Добавлено отображение статуса работы на иконке.
* Добавлена возможность автоматического обновления.
## 1.2.3
* Устранена возможная причина падения.
* Добавлена информация о версии и некоторые сообщения об ошибках.
## 1.2.2
* Добавлен альтернативный источник перевода.
## 1.2.1
* Устранена ошибка отсутствия перевода.
* Устранена ошибка использования языка распознавания по умолчанию при выборе другого в окне выделения области распознавания.
## 1.2.0
* Изменен установщик.
* В установщик добавлены все доступные языки для распознавания.
* Добавлена возможность указания языка при выборе области распознавания при помощи выделения с правым кликом.
* Человекочитаемые названия языков.
* Уменьшено потребление памяти.
* Обновлены библиотеки.
## 1.1.3
* В установщик добавлена библиотека libgcc_s_dw2-1.dll.
* Обновлены библиотеки.
## 1.1.2
* При задании в настройках пути к tessdata символы «\» или «/» в конце пути теперь не обязательны.
## 1.1.1
* Пофиксен баг с неверным размером окна отображения результатов.
## 1.1.0
* Отображение результата в окошке, вместе с картинкой.
* Контекстное меню расширено. Добавлены кнопки отображения последнего результата и копирования его в буфер обмена.

View File

@ -10,8 +10,7 @@ if len(sys.argv) > 1 and sys.argv[1] == 'glibc_version': # subcommand
sub.run('ldd --version | head -n 1 | grep -Po "\\d\\.\\d\\d"', shell=True) sub.run('ldd --version | head -n 1 | grep -Po "\\d\\.\\d\\d"', shell=True)
exit(0) exit(0)
tag = os.environ.get('TAG', '') artifact_name = '{}-{}.AppImage'.format(app_name, app_version)
artifact_name = '{}-{}{}.AppImage'.format(app_name, app_version, tag)
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
c.print(artifact_name) c.print(artifact_name)
exit(0) exit(0)
@ -22,7 +21,7 @@ c.print('>> Making appimage')
base_url = 'https://github.com/probonopd/linuxdeployqt/releases/download' base_url = 'https://github.com/probonopd/linuxdeployqt/releases/download'
continuous_url = base_url + '/continuous/linuxdeployqt-continuous-x86_64.AppImage' continuous_url = base_url + '/continuous/linuxdeployqt-continuous-x86_64.AppImage'
tagged_url = base_url + '/6/linuxdeployqt-6-x86_64.AppImage' tagged_url = base_url + '/6/linuxdeployqt-6-x86_64.AppImage'
linuxdeployqt_url = continuous_url linuxdeployqt_url = tagged_url
linuxdeployqt_original = os.path.basename(linuxdeployqt_url) linuxdeployqt_original = os.path.basename(linuxdeployqt_url)
c.download(linuxdeployqt_url, linuxdeployqt_original) c.download(linuxdeployqt_url, linuxdeployqt_original)
@ -46,20 +45,13 @@ os.environ['VERSION'] = app_version
# debug flags: -unsupported-bundle-everything -unsupported-allow-new-glibc # debug flags: -unsupported-bundle-everything -unsupported-allow-new-glibc
flags = '' if os.getenv("DEBUG") is None else '-unsupported-allow-new-glibc' flags = '' if os.getenv("DEBUG") is None else '-unsupported-allow-new-glibc'
additional_files = glob(ssl_dir + '/lib/lib*.so.*') + \
glob('/usr/lib/x86_64-linux-gnu/nss/*')
out_lib_dir = install_dir + '/usr/lib' out_lib_dir = install_dir + '/usr/lib'
os.makedirs(out_lib_dir, exist_ok=True) os.makedirs(out_lib_dir, exist_ok=True)
for f in additional_files: for f in glob(ssl_dir + '/lib/lib*.so.*'):
c.print('>> Copying {} to {}'.format(f, out_lib_dir)) c.print('>> Copying {} to {}'.format(f, out_lib_dir))
shutil.copy(f, out_lib_dir) shutil.copy(f, out_lib_dir)
c.ensure_got_path('{}/usr/share/doc/libc6/copyright'.format(install_dir))
c.run('{} {}/usr/share/applications/*.desktop {} -appimage -qmake={}/bin/qmake'.format( c.run('{} {}/usr/share/applications/*.desktop {} -appimage -qmake={}/bin/qmake'.format(
linuxdeployqt_bin, install_dir, flags, qt_dir)) linuxdeployqt_bin, install_dir, flags, qt_dir))
c.run('mv {}-{}*.AppImage "{}"'.format(app_name, app_version, artifact_path)) c.run('mv {}-{}*.AppImage "{}"'.format(app_name, app_version, artifact_path))
bin_path = install_dir + '/usr/bin/' + bin_name
c.print('>> Md5 {} {}'.format(bin_path, c.md5sum(bin_path)))

View File

@ -10,7 +10,6 @@ import multiprocessing
import platform import platform
import re import re
import ast import ast
import hashlib
print = functools.partial(print, flush=True) print = functools.partial(print, flush=True)
@ -150,7 +149,15 @@ def get_msvc_env_cmd(bitness='64', msvc_version=''):
if platform.system() != "Windows": if platform.system() != "Windows":
return None return None
env_script = msvc_version + '/VC/Auxiliary/Build/vcvars{}.bat'.format(bitness) msvc_path = 'C:/Program Files (x86)/Microsoft Visual Studio'
if len(msvc_version) == 0:
with os.scandir(msvc_path) as ver_it:
version = next(ver_it, '')
with os.scandir(msvc_path + '/' + version) as ed_it:
msvc_version = version + '/' + next(ed_it, '')
env_script = msvc_path + '/{}/VC/Auxiliary/Build/vcvars{}.bat'.format(
msvc_version, bitness)
return '"' + env_script + '"' return '"' + env_script + '"'
@ -201,13 +208,3 @@ def apply_cmd_env(cmd):
print('>>> Changing env', key, '\nfrom\n', print('>>> Changing env', key, '\nfrom\n',
os.environ[key], '\nto\n', value) os.environ[key], '\nto\n', value)
os.environ[key] = value os.environ[key] = value
def md5sum(path):
if not os.path.exists(path):
return ''
md5 = hashlib.md5()
with open(path, 'rb') as f:
md5.update(f.read())
return md5.hexdigest()
return ''

View File

@ -4,7 +4,7 @@ import re
app_name = 'ScreenTranslator' app_name = 'ScreenTranslator'
target_name = app_name target_name = app_name
qt_version = '5.15.2' qt_version = '5.14.0'
qt_modules = ['qtbase', 'qttools', 'icu', qt_modules = ['qtbase', 'qttools', 'icu',
'qttranslations', 'qtx11extras', 'qtwebengine', 'qtwebchannel', 'qttranslations', 'qtx11extras', 'qtwebengine', 'qtwebchannel',
'qtdeclarative', 'qtlocation', 'opengl32sw', 'd3dcompiler_47', 'qtdeclarative', 'qtlocation', 'opengl32sw', 'd3dcompiler_47',
@ -18,7 +18,6 @@ pro_file = path.abspath(path.dirname(__file__) +
'/../../screen-translator.pro') '/../../screen-translator.pro')
test_pro_file = path.abspath(path.dirname(__file__) + test_pro_file = path.abspath(path.dirname(__file__) +
'/../../tests/tests.pro') '/../../tests/tests.pro')
bin_name = 'screen-translator'
app_version = 'testing' app_version = 'testing'
with open(pro_file, 'r') as f: with open(pro_file, 'r') as f:
match = re.search(r'VER=(.*)', f.read()) match = re.search(r'VER=(.*)', f.read())
@ -30,6 +29,6 @@ os_name = getenv('OS', 'linux')
app_version += {'linux': '', 'macos': '-experimental', app_version += {'linux': '', 'macos': '-experimental',
'win32': '', 'win64': ''}[os_name] 'win32': '', 'win64': ''}[os_name]
bitness = '32' if os_name == 'win32' else '64' bitness = '32' if os_name == 'win32' else '64'
msvc_version = getenv('MSVC_VERSION', 'C:/Program Files (x86)/Microsoft Visual Studio/2019/Community') msvc_version = getenv('MSVC_VERSION', '2017/Community')
build_type = 'release' # 'debug' build_type = 'release' # 'debug'

View File

@ -10,33 +10,22 @@ url = 'https://github.com/hunspell/hunspell/files/2573619/hunspell-1.7.0.tar.gz'
required_version = '1.7.0' required_version = '1.7.0'
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
cache_file = install_dir + '/hunspell.cache'
cache_file_data = required_version + build_type_flag
def check_existing(): def check_existing():
if not os.path.exists(cache_file):
return False
with open(cache_file, 'r') as f:
cached = f.read()
if cached != cache_file_data:
return False
if platform.system() == "Windows": if platform.system() == "Windows":
dll = install_dir + '/bin/hunspell.dll' dll = install_dir + '/bin/hunspell.dll'
lib = install_dir + '/lib/hunspell.lib' lib = install_dir + '/lib/hunspell.lib'
if not os.path.exists(dll) or not os.path.exists(lib): if not os.path.exists(dll) or not os.path.exists(lib):
return False return False
elif platform.system() == "Darwin": elif platform.system() == "Darwin":
lib = install_dir + '/lib/libhunspell.dylib' lib = install_dir + '/lib/libhunspell.1.7.0.dylib'
if not os.path.exists(lib): if not os.path.exists(lib):
return False return False
c.symlink(lib, install_dir + '/lib/libhunspell.dylib')
else: else:
lib = install_dir + '/lib/libhunspell.so' lib = install_dir + '/lib/libhunspell-1.7.so'
if not os.path.exists(lib): if not os.path.exists(lib):
return False return False
c.symlink(lib, install_dir + '/lib/libhunspell.so')
includes_path = install_dir + '/include/hunspell' includes_path = install_dir + '/include/hunspell'
if len(c.get_folder_files(includes_path)) == 0: if len(c.get_folder_files(includes_path)) == 0:
@ -76,6 +65,12 @@ os.chdir(build_dir)
c.set_make_threaded() c.set_make_threaded()
if platform.system() != "Windows":
c.run('autoreconf -i {}'.format(src_dir))
c.run('{}/configure --prefix={}'.format(src_dir, install_dir))
c.run('make')
c.run('make install')
else:
lib_src = os.path.join(src_dir, 'src', 'hunspell') lib_src = os.path.join(src_dir, 'src', 'hunspell')
sources = [] sources = []
with os.scandir(lib_src) as it: with os.scandir(lib_src) as it:
@ -83,8 +78,10 @@ with os.scandir(lib_src) as it:
if not f.is_file() or not f.name.endswith('.cxx'): if not f.is_file() or not f.name.endswith('.cxx'):
continue continue
sources.append('${SRC_DIR}/' + f.name) sources.append('${SRC_DIR}/' + f.name)
headers = ['${SRC_DIR}/atypes.hxx', '${SRC_DIR}/hunspell.h', '${SRC_DIR}/hunspell.hxx', headers = ['${SRC_DIR}/atypes.hxx', '${SRC_DIR}/hunspell.h', '${SRC_DIR}/hunspell.hxx',
'${SRC_DIR}/hunvisapi.h', '${SRC_DIR}/w_char.hxx'] '${SRC_DIR}/hunvisapi.h', '${SRC_DIR}/w_char.hxx']
cmake_file = os.path.join(build_dir, 'CMakeLists.txt') cmake_file = os.path.join(build_dir, 'CMakeLists.txt')
with open(cmake_file, 'w') as f: with open(cmake_file, 'w') as f:
f.write('project(hunspell)\n') f.write('project(hunspell)\n')
@ -93,9 +90,7 @@ with open(cmake_file, 'w') as f:
f.write('\n') f.write('\n')
f.write('add_library(hunspell SHARED {})\n'.format(' '.join(sources))) f.write('add_library(hunspell SHARED {})\n'.format(' '.join(sources)))
f.write('\n') f.write('\n')
f.write('add_compile_definitions(HAVE_CONFIG_H BUILDING_LIBHUNSPELL)\n') f.write('add_compile_definitions(HAVE_CONFIG_H _WIN32 BUILDING_LIBHUNSPELL)\n')
if platform.system() == "Windows":
f.write('add_compile_definitions(_WIN32)\n')
f.write('\n') f.write('\n')
f.write('install(FILES {} \ f.write('install(FILES {} \
DESTINATION include/hunspell)\n'.format(' '.join(headers))) DESTINATION include/hunspell)\n'.format(' '.join(headers)))
@ -110,22 +105,15 @@ ${{CMAKE_CURRENT_BINARY_DIR}}/hunspell.pc @ONLY)\n'.format(src_dir.replace('\\',
f.write('install(FILES ${CMAKE_CURRENT_BINARY_DIR}/hunspell.pc \ f.write('install(FILES ${CMAKE_CURRENT_BINARY_DIR}/hunspell.pc \
DESTINATION lib/pkgconfig)\n') DESTINATION lib/pkgconfig)\n')
cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}" {}'.format(
build_dir, install_dir, c.get_cmake_arch_args(bitness=bitness))
if platform.system() == "Windows":
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version) env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
c.apply_cmd_env(env_cmd) c.apply_cmd_env(env_cmd)
cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}" {}'.format(
c.set_make_threaded() build_dir, install_dir, c.get_cmake_arch_args(bitness=bitness))
c.run('cmake {}'.format(cmake_args)) c.run('cmake {}'.format(cmake_args))
build_type_flag = 'Debug' if build_type == 'debug' else 'Release' build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
c.run('cmake --build . --config {}'.format(build_type_flag)) c.run('cmake --build . --config {}'.format(build_type_flag))
c.run('cmake --build . --target install --config {}'.format(build_type_flag)) c.run('cmake --build . --target install --config {}'.format(build_type_flag))
with open(cache_file, 'w') as f:
f.write(cache_file_data)
if not check_existing(): # create links if not check_existing(): # create links
c.print('>> Build failed') c.print('>> Build failed')
exit(1) exit(1)

View File

@ -6,33 +6,20 @@ import platform
c.print('>> Installing leptonica') c.print('>> Installing leptonica')
install_dir = dependencies_dir install_dir = dependencies_dir
url = 'https://github.com/DanBloomberg/leptonica/releases/download/1.82.0/leptonica-1.82.0.tar.gz' url = 'http://www.leptonica.org/source/leptonica-1.78.0.tar.gz'
required_version = '1.82.0' required_version = '1.78.0'
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
cache_file = install_dir + '/leptonica.cache'
cache_file_data = required_version + build_type_flag
def check_existing(): def check_existing():
if not os.path.exists(cache_file):
return False
with open(cache_file, 'r') as f:
cached = f.read()
if cached != cache_file_data:
return False
if platform.system() == "Windows": if platform.system() == "Windows":
dll = install_dir + '/bin/leptonica-1.82.0.dll' dll = install_dir + '/bin/leptonica-1.78.0.dll'
lib = install_dir + '/lib/leptonica-1.82.0.lib' lib = install_dir + '/lib/leptonica-1.78.0.lib'
if not os.path.exists(dll) or not os.path.exists(lib): if not os.path.exists(dll) or not os.path.exists(lib):
return False return False
c.symlink(dll, install_dir + '/bin/leptonica.dll') c.symlink(dll, install_dir + '/bin/leptonica.dll')
c.symlink(lib, install_dir + '/lib/leptonica.lib') c.symlink(lib, install_dir + '/lib/leptonica.lib')
elif platform.system() == "Darwin": elif platform.system() == "Darwin":
lib = install_dir + '/lib/libleptonica.1.82.0.dylib' lib = install_dir + '/lib/libleptonica.1.78.0.dylib'
if not os.path.exists(lib): if not os.path.exists(lib):
return False return False
c.symlink(lib, install_dir + '/lib/libleptonica.dylib') c.symlink(lib, install_dir + '/lib/libleptonica.dylib')
@ -44,12 +31,12 @@ def check_existing():
if len(c.get_folder_files(includes_path)) == 0: if len(c.get_folder_files(includes_path)) == 0:
return False return False
version_file = install_dir + '/lib/cmake/leptonica/LeptonicaConfig-version.cmake' version_file = install_dir + '/cmake/LeptonicaConfig-version.cmake'
if not os.path.exists(version_file): if not os.path.exists(version_file):
return False return False
with open(version_file, 'rt') as f: with open(version_file, 'rt') as f:
existing_version = f.readline()[22:28] # set(Leptonica_VERSION 1.82.0) existing_version = f.readline()[22:28] # set(Leptonica_VERSION 1.78.0)
if existing_version != required_version: if existing_version != required_version:
return False return False
return True return True
@ -66,20 +53,12 @@ src_dir = os.path.abspath('leptonica_src')
c.extract(archive, '.') c.extract(archive, '.')
c.symlink(c.get_archive_top_dir(archive), src_dir) c.symlink(c.get_archive_top_dir(archive), src_dir)
with open('{}/CMakeLists.txt'.format(src_dir), 'r+') as f:
data = f.read()
data = data.replace('pkg_check_modules(WEBP', '#pkg_check_modules(WEBP')
data = data.replace('if(NOT WEBP', 'if(FALSE')
f.seek(0, os.SEEK_SET)
f.write(data)
c.ensure_got_path(install_dir) c.ensure_got_path(install_dir)
c.recreate_dir(build_dir) c.recreate_dir(build_dir)
os.chdir(build_dir) os.chdir(build_dir)
cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}" -DBUILD_SHARED_LIBS=ON \ cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}"'.format(src_dir, install_dir)
-DSW_BUILD=OFF'.format(src_dir, install_dir,)
if platform.system() == "Windows": if platform.system() == "Windows":
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version) env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
@ -92,9 +71,6 @@ build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
c.run('cmake --build . --config {}'.format(build_type_flag)) c.run('cmake --build . --config {}'.format(build_type_flag))
c.run('cmake --build . --target install --config {}'.format(build_type_flag)) c.run('cmake --build . --target install --config {}'.format(build_type_flag))
with open(cache_file, 'w') as f:
f.write(cache_file_data)
if not check_existing(): # create links if not check_existing(): # create links
c.print('>> Build failed') c.print('>> Build failed')
exit(1) exit(1)

View File

@ -12,12 +12,12 @@ if os_name == 'linux':
qt_dir_prefix = '{}/gcc_64'.format(qt_version) qt_dir_prefix = '{}/gcc_64'.format(qt_version)
elif os_name == 'win32': elif os_name == 'win32':
os_url = 'windows_x86' os_url = 'windows_x86'
kit_arch = 'win32_msvc2019' kit_arch = 'win32_msvc2017'
qt_dir_prefix = '{}/msvc2019'.format(qt_version) qt_dir_prefix = '{}/msvc2017'.format(qt_version)
elif os_name == 'win64': elif os_name == 'win64':
os_url = 'windows_x86' os_url = 'windows_x86'
kit_arch = 'win64_msvc2019_64' kit_arch = 'win64_msvc2017_64'
qt_dir_prefix = '{}/msvc2019_64'.format(qt_version) qt_dir_prefix = '{}/msvc2017_64'.format(qt_version)
elif os_name == 'macos': elif os_name == 'macos':
os_url = 'mac_x64' os_url = 'mac_x64'
kit_arch = 'clang_64' kit_arch = 'clang_64'

View File

@ -6,49 +6,43 @@ import platform
c.print('>> Installing tesseract') c.print('>> Installing tesseract')
install_dir = dependencies_dir install_dir = dependencies_dir
required_version = '5.2.0' url = 'https://github.com/tesseract-ocr/tesseract/archive/4.1.1.tar.gz'
url = 'https://github.com/tesseract-ocr/tesseract/archive/{}.tar.gz'.format(required_version) required_version = '4.1.1'
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
cache_file = install_dir + '/tesseract.cache'
cache_file_data = required_version + build_type_flag
def check_existing(): def check_existing():
if not os.path.exists(cache_file): if platform.system() == "Windows":
dll = install_dir + '/bin/tesseract41.dll'
lib = install_dir + '/lib/tesseract41.lib'
if not os.path.exists(dll) or not os.path.exists(lib):
return False return False
with open(cache_file, 'r') as f: c.symlink(dll, install_dir + '/bin/tesseract.dll')
cached = f.read() c.symlink(lib, install_dir + '/lib/tesseract.lib')
if cached != cache_file_data: elif platform.system() == "Darwin":
lib = install_dir + '/lib/libtesseract.4.1.1.dylib'
if not os.path.exists(lib):
return False
c.symlink(lib, install_dir + '/lib/libtesseract.dylib')
else:
if not os.path.exists(install_dir + '/lib/libtesseract.so'):
return False return False
includes_path = install_dir + '/include/tesseract' includes_path = install_dir + '/include/tesseract'
if len(c.get_folder_files(includes_path)) == 0: if len(c.get_folder_files(includes_path)) == 0:
return False return False
if platform.system() == "Windows": version_file = install_dir + '/cmake/TesseractConfig-version.cmake'
file_name_ver = required_version[0] + required_version[2] if not os.path.exists(version_file):
dll = install_dir + '/bin/tesseract{}.dll'.format(file_name_ver)
lib = install_dir + '/lib/tesseract{}.lib'.format(file_name_ver)
if not os.path.exists(dll) or not os.path.exists(lib):
return False return False
c.symlink(dll, install_dir + '/bin/tesseract.dll')
c.symlink(lib, install_dir + '/lib/tesseract.lib')
elif platform.system() == "Darwin":
lib = install_dir + '/lib/libtesseract.{}.dylib'.format(required_version)
if not os.path.exists(lib):
return False
c.symlink(lib, install_dir + '/lib/libtesseract.dylib')
else:
lib = install_dir + '/lib/libtesseract.so.{}'.format(required_version)
if not os.path.exists(lib):
return False
c.symlink(lib, install_dir + '/lib/libtesseract.so')
with open(version_file, 'rt') as f:
existing_version = f.readline()[22:27] # set(Tesseract_VERSION 1.78.0)
if existing_version != required_version:
return False
return True return True
if check_existing() and not 'FORCE' in os.environ: if check_existing():
c.print('>> Using cached') c.print('>> Using cached')
exit(0) exit(0)
@ -64,20 +58,8 @@ c.ensure_got_path(install_dir)
c.recreate_dir(build_dir) c.recreate_dir(build_dir)
os.chdir(build_dir) os.chdir(build_dir)
cmake_args = '"{0}" \ cmake_args = '"{0}" -DCMAKE_INSTALL_PREFIX="{1}" -DLeptonica_DIR="{1}/cmake" \
-DCMAKE_INSTALL_PREFIX="{1}" \ -DBUILD_TRAINING_TOOLS=OFF -DBUILD_TESTS=OFF'.format(src_dir, install_dir)
-DLeptonica_DIR="{1}/cmake" \
-DSW_BUILD=OFF \
-DBUILD_TRAINING_TOOLS=OFF \
-DBUILD_TESTS=OFF \
-DBUILD_SHARED_LIBS=ON \
-DDISABLE_CURL=ON \
-DDISABLE_ARCHIVE=ON \
-DUSE_SYSTEM_ICU=ON \
-DENABLE_LTO=ON \
-DGRAPHICS_DISABLED=ON \
-DDISABLED_LEGACY_ENGINE=ON \
'.format(src_dir, install_dir)
if platform.system() == "Windows": if platform.system() == "Windows":
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version) env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
@ -86,13 +68,10 @@ if platform.system() == "Windows":
c.set_make_threaded() c.set_make_threaded()
c.run('cmake {}'.format(cmake_args)) c.run('cmake {}'.format(cmake_args))
build_type_flag = 'Debug' if build_type == 'debug' else 'Release'
c.run('cmake --build . --config {}'.format(build_type_flag)) c.run('cmake --build . --config {}'.format(build_type_flag))
c.run('cmake --build . --target install --config {}'.format(build_type_flag)) c.run('cmake --build . --target install --config {}'.format(build_type_flag))
with open(cache_file, 'w') as f: if not check_existing(): # create links
f.write(cache_file_data)
if not check_existing(): # add suffix
c.print('>> Build failed') c.print('>> Build failed')
exit(1) exit(1)

View File

@ -3,8 +3,7 @@ from config import *
import os import os
import sys import sys
tag = os.environ.get('TAG', '') artifact_name = '{}-{}.dmg'.format(app_name, app_version)
artifact_name = '{}-{}{}.dmg'.format(app_name, app_version, tag)
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
c.print(artifact_name) c.print(artifact_name)
exit(0) exit(0)

View File

@ -1,43 +0,0 @@
import os
import platform
import sys
import subprocess
here = os.path.dirname(__file__)
def r_out(script, args):
return subprocess.run([sys.executable, os.path.join(here, script)] + args, check=True, stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name':
artifact_name = ''
if platform.system() == "Linux":
artifact_name = r_out('appimage.py', ['artifact_name'])
if platform.system() == "Windows":
artifact_name = r_out('windeploy.py', ['artifact_name'])
if platform.system() == "Darwin":
artifact_name = r_out('macdeploy.py', ['artifact_name'])
print(artifact_name)
exit(0)
def r(script):
return subprocess.run([sys.executable, os.path.join(here, script)], check=True)
r('get_qt.py')
r('get_qt_ssl.py')
r('get_leptonica.py')
r('get_tesseract.py')
r('get_hunspell.py')
r('test.py')
r('build.py')
if platform.system() == "Linux":
r('appimage.py')
if platform.system() == "Windows":
r('windeploy.py')
if platform.system() == "Darwin":
r('macdeploy.py')

View File

@ -5,8 +5,7 @@ import sys
import shutil import shutil
from glob import glob from glob import glob
tag = os.environ.get('TAG', '') artifact_name = '{}-{}-{}.zip'.format(app_name, app_version, os_name)
artifact_name = '{}-{}{}-{}.zip'.format(app_name, app_version, tag, os_name)
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
c.print(artifact_name) c.print(artifact_name)
exit(0) exit(0)
@ -27,17 +26,6 @@ c.recreate_dir(install_dir)
c.run('nmake INSTALL_ROOT="{0}" DESTDIR="{0}" install'.format(install_dir)) c.run('nmake INSTALL_ROOT="{0}" DESTDIR="{0}" install'.format(install_dir))
c.run('{}/bin/windeployqt.exe "{}"'.format(qt_dir, install_dir)) c.run('{}/bin/windeployqt.exe "{}"'.format(qt_dir, install_dir))
vcredist_for_ssl_url = ''
vcredist_for_ssl_file = ''
if bitness == '32':
vcredist_for_ssl_url = 'https://download.microsoft.com/download/C/6/D/C6D0FD4E-9E53-4897-9B91-836EBA2AACD3/vcredist_x86.exe'
vcredist_for_ssl_file = 'vc_redist.x86.2010.exe'
else:
vcredist_for_ssl_url = 'https://download.microsoft.com/download/A/8/0/A80747C3-41BD-45DF-B505-E9710D2744E0/vcredist_x64.exe'
vcredist_for_ssl_file = 'vc_redist.x64.2010.exe'
c.download(vcredist_for_ssl_url, os.path.join(install_dir, vcredist_for_ssl_file))
libs_dir = os.path.join(dependencies_dir, 'bin') libs_dir = os.path.join(dependencies_dir, 'bin')
for file in os.scandir(libs_dir): for file in os.scandir(libs_dir):
if file.is_file(follow_symlinks=False) and file.name.endswith('.dll'): if file.is_file(follow_symlinks=False) and file.name.endswith('.dll'):
@ -49,9 +37,4 @@ for f in glob(ssl_dir + '/bin/*.dll'):
c.print('>> Copying {} to {}'.format(f, install_dir)) c.print('>> Copying {} to {}'.format(f, install_dir))
shutil.copy(f, install_dir) shutil.copy(f, install_dir)
open(os.path.join(install_dir, 'qt.conf'), 'a').close() # fix for non-latin paths
c.archive(c.get_folder_files(os.path.relpath(install_dir)), artifact_path) c.archive(c.get_folder_files(os.path.relpath(install_dir)), artifact_path)
bin_path = install_dir + '\\' + bin_name + '.exe'
c.print('>> Md5 {} {}'.format(bin_path, c.md5sum(bin_path)))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -77,7 +77,7 @@ for d in it:
files[lang] = [aff, dic] files[lang] = [aff, dic]
print(',"correction": {') print(',"hunspell": {')
comma = '' comma = ''
unknown_names = [] unknown_names = []
for lang in sorted(files.keys()): for lang in sorted(files.keys()):

View File

@ -12,8 +12,7 @@ CaptureArea::CaptureArea(const QRect &rect, const Settings &settings)
{ {
} }
TaskPtr CaptureArea::task(const QPixmap &pixmap, TaskPtr CaptureArea::task(const QPixmap &pixmap) const
const QPoint &pixmapOffset) const
{ {
if (pixmap.isNull() || !isValid()) if (pixmap.isNull() || !isValid())
return {}; return {};
@ -22,18 +21,11 @@ TaskPtr CaptureArea::task(const QPixmap &pixmap,
task->generation = generation_; task->generation = generation_;
task->useHunspell = useHunspell_; task->useHunspell = useHunspell_;
task->captured = pixmap.copy(rect_); task->captured = pixmap.copy(rect_);
task->capturePoint = pixmapOffset + rect_.topLeft(); task->capturePoint = rect_.topLeft();
task->sourceLanguage = sourceLanguage_; task->sourceLanguage = sourceLanguage_;
if (task->sourceLanguage.isEmpty())
task->error += QObject::tr("No source language set");
if (doTranslation_ && !translators_.isEmpty()) { if (doTranslation_ && !translators_.isEmpty()) {
task->targetLanguage = targetLanguage_; task->targetLanguage = targetLanguage_;
task->translators = translators_; task->translators = translators_;
if (task->targetLanguage.isEmpty()) {
task->error += (task->error.isEmpty() ? "" : ", ") +
QObject::tr("No target language set");
}
} }
return task; return task;

View File

@ -11,7 +11,7 @@ class CaptureArea
{ {
public: public:
CaptureArea(const QRect& rect, const Settings& settings); CaptureArea(const QRect& rect, const Settings& settings);
TaskPtr task(const QPixmap& pixmap, const QPoint& pixmapOffset) const; TaskPtr task(const QPixmap& pixmap) const;
void setGeneration(uint generation); void setGeneration(uint generation);
bool isValid() const; bool isValid() const;

View File

@ -22,12 +22,10 @@ static bool notLocked(const std::shared_ptr<CaptureArea> &area)
CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer, CaptureAreaSelector::CaptureAreaSelector(Capturer &capturer,
const Settings &settings, const Settings &settings,
const CommonModels &models, const CommonModels &models,
const QPixmap &pixmap, const QPixmap &pixmap)
const QPoint &pixmapOffset)
: capturer_(capturer) : capturer_(capturer)
, settings_(settings) , settings_(settings)
, pixmap_(pixmap) , pixmap_(pixmap)
, pixmapOffset_(pixmapOffset)
, editor_(std::make_unique<CaptureAreaEditor>(models, this)) , editor_(std::make_unique<CaptureAreaEditor>(models, this))
, contextMenu_(new QMenu(this)) , contextMenu_(new QMenu(this))
{ {
@ -58,7 +56,7 @@ CaptureAreaSelector::~CaptureAreaSelector() = default;
void CaptureAreaSelector::activate() void CaptureAreaSelector::activate()
{ {
setGeometry(QRect(pixmapOffset_, pixmap_.size())); setGeometry(pixmap_.rect());
show(); show();
activateWindow(); activateWindow();
} }
@ -238,16 +236,11 @@ void CaptureAreaSelector::hideEvent(QHideEvent * /*event*/)
void CaptureAreaSelector::keyPressEvent(QKeyEvent *event) void CaptureAreaSelector::keyPressEvent(QKeyEvent *event)
{ {
if (event->key() == Qt::Key_Escape) { if (event->key() == Qt::Key_Escape) {
if (editor_ && editor_->isVisible())
applyEditor();
cancel(); cancel();
return; return;
} }
if (event->key() == Qt::Key_Return) { if (event->key() == Qt::Key_Return) {
if (editor_ && editor_->isVisible())
applyEditor();
if (!areas_.empty()) { if (!areas_.empty()) {
captureAll(); captureAll();
} else { } else {
@ -339,7 +332,7 @@ void CaptureAreaSelector::customize(const std::shared_ptr<CaptureArea> &area)
edited_ = area; edited_ = area;
editor_->show(); editor_->show();
const auto topLeft = service::geometry::cornerAtPoint( const auto topLeft = service::geometry::cornerAtPoint(
area->rect().center(), editor_->size(), QRect({}, size())); area->rect().center(), editor_->size(), geometry());
editor_->move(topLeft); editor_->move(topLeft);
update(); update();
} }

View File

@ -12,8 +12,7 @@ class CaptureAreaSelector : public QWidget
public: public:
CaptureAreaSelector(Capturer &capturer, const Settings &settings, CaptureAreaSelector(Capturer &capturer, const Settings &settings,
const CommonModels &models, const QPixmap &pixmap, const CommonModels &models, const QPixmap &pixmap);
const QPoint &pixmapOffset);
~CaptureAreaSelector(); ~CaptureAreaSelector();
void activate(); void activate();
@ -51,7 +50,6 @@ private:
Capturer &capturer_; Capturer &capturer_;
const Settings &settings_; const Settings &settings_;
const QPixmap &pixmap_; const QPixmap &pixmap_;
const QPoint &pixmapOffset_;
Generation generation_{}; Generation generation_{};
QPoint startSelectPos_; QPoint startSelectPos_;
QPoint currentSelectPos_; QPoint currentSelectPos_;

View File

@ -15,7 +15,7 @@ Capturer::Capturer(Manager &manager, const Settings &settings,
: manager_(manager) : manager_(manager)
, settings_(settings) , settings_(settings)
, selector_(std::make_unique<CaptureAreaSelector>(*this, settings_, models, , selector_(std::make_unique<CaptureAreaSelector>(*this, settings_, models,
pixmap_, pixmapOffset_)) pixmap_))
{ {
} }
@ -56,7 +56,6 @@ void Capturer::updatePixmap()
QPixmap combined(rect.size()); QPixmap combined(rect.size());
QPainter p(&combined); QPainter p(&combined);
p.translate(-rect.topLeft());
for (const auto screen : screens) { for (const auto screen : screens) {
const auto geometry = screen->geometry(); const auto geometry = screen->geometry();
@ -67,9 +66,6 @@ void Capturer::updatePixmap()
SOFT_ASSERT(selector_, return ); SOFT_ASSERT(selector_, return );
pixmap_ = combined; pixmap_ = combined;
pixmapOffset_ = rect.topLeft();
for (auto &r : screenRects) r.translate(-rect.topLeft());
selector_->setScreenRects(screenRects); selector_->setScreenRects(screenRects);
} }
@ -91,7 +87,7 @@ void Capturer::selected(const CaptureArea &area)
selector_->hide(); selector_->hide();
SOFT_ASSERT(!pixmap_.isNull(), return manager_.captureCanceled()) SOFT_ASSERT(!pixmap_.isNull(), return manager_.captureCanceled())
auto task = area.task(pixmap_, pixmapOffset_); auto task = area.task(pixmap_);
if (task) if (task)
manager_.captured(task); manager_.captured(task);
else else

View File

@ -26,6 +26,5 @@ private:
Manager &manager_; Manager &manager_;
const Settings &settings_; const Settings &settings_;
QPixmap pixmap_; QPixmap pixmap_;
QPoint pixmapOffset_;
std::unique_ptr<CaptureAreaSelector> selector_; std::unique_ptr<CaptureAreaSelector> selector_;
}; };

View File

@ -11,8 +11,7 @@ CommonModels::CommonModels()
CommonModels::~CommonModels() = default; CommonModels::~CommonModels() = default;
void CommonModels::update(const QString &tessdataPath, void CommonModels::update(const QString &tessdataPath)
const QString &translatorPath)
{ {
{ {
auto names = Tesseract::availableLanguageNames(tessdataPath); auto names = Tesseract::availableLanguageNames(tessdataPath);
@ -20,11 +19,6 @@ void CommonModels::update(const QString &tessdataPath,
sourceLanguageModel_->setStringList(names); sourceLanguageModel_->setStringList(names);
} }
{
translators_ = Translator::availableTranslators(translatorPath);
std::sort(translators_.begin(), translators_.end());
}
if (targetLanguageModel_->rowCount() > 0) if (targetLanguageModel_->rowCount() > 0)
return; return;
@ -44,8 +38,3 @@ QStringListModel *CommonModels::targetLanguageModel() const
{ {
return targetLanguageModel_.get(); return targetLanguageModel_.get();
} }
const QStringList &CommonModels::translators() const
{
return translators_;
}

View File

@ -12,14 +12,12 @@ public:
CommonModels(); CommonModels();
~CommonModels(); ~CommonModels();
void update(const QString& tessdataPath, const QString& translatorPath); void update(const QString& tessdataPath);
QStringListModel* sourceLanguageModel() const; QStringListModel* sourceLanguageModel() const;
QStringListModel* targetLanguageModel() const; QStringListModel* targetLanguageModel() const;
const QStringList& translators() const;
private: private:
std::unique_ptr<QStringListModel> sourceLanguageModel_; std::unique_ptr<QStringListModel> sourceLanguageModel_;
std::unique_ptr<QStringListModel> targetLanguageModel_; std::unique_ptr<QStringListModel> targetLanguageModel_;
QStringList translators_;
}; };

View File

@ -31,65 +31,41 @@ Corrector::~Corrector()
{ {
workerThread_->quit(); workerThread_->quit();
const auto timeoutMs = 2000; const auto timeoutMs = 2000;
if (!workerThread_->wait(timeoutMs)) { if (!workerThread_->wait(timeoutMs))
LTRACE() << "terminating hunspell thread";
workerThread_->terminate(); workerThread_->terminate();
} }
}
void Corrector::correct(const TaskPtr &task) void Corrector::correct(const TaskPtr &task)
{ {
SOFT_ASSERT(task, return ); SOFT_ASSERT(task, return );
SOFT_ASSERT(task->isValid(), return ); SOFT_ASSERT(task->isValid(), return );
queue_.push_back(task);
if (task->recognized.isEmpty()) { if (task->recognized.isEmpty()) {
finishCorrection(task); manager_.corrected(task);
return; return;
} }
task->corrected = task->recognized; task->corrected = task->recognized;
if (settings_.useUserSubstitutions && !settings_.userSubstitutions.empty()) { if (!settings_.userSubstitutions.empty())
task->corrected = substituteUser(task->recognized, task->sourceLanguage); task->corrected = substituteUser(task->recognized, task->sourceLanguage);
LTRACE() << "Corrected with user data";
if (task->useHunspell) {
emit correctAuto(task);
return;
} }
if (!task->useHunspell) {
finishCorrection(task); finishCorrection(task);
return;
}
if (queue_.size() == 1)
processQueue();
}
void Corrector::processQueue()
{
if (queue_.empty())
return;
emit correctAuto(queue_.front());
} }
void Corrector::updateSettings() void Corrector::updateSettings()
{ {
queue_.clear(); emit resetAuto(settings_.hunspellDir);
emit resetAuto(settings_.hunspellPath);
} }
void Corrector::finishCorrection(const TaskPtr &task) void Corrector::finishCorrection(const TaskPtr &task)
{ {
manager_.corrected(task); manager_.corrected(task);
SOFT_ASSERT(!queue_.empty(), return );
if (queue_.front() == task) {
queue_.pop_front();
} else {
LERROR() << "processed not first item in correction queue";
queue_.clear();
}
processQueue();
} }
QString Corrector::substituteUser(const QString &source, QString Corrector::substituteUser(const QString &source,

View File

@ -4,8 +4,6 @@
#include <QObject> #include <QObject>
#include <deque>
class Corrector : public QObject class Corrector : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -24,10 +22,8 @@ private:
void finishCorrection(const TaskPtr &task); void finishCorrection(const TaskPtr &task);
QString substituteUser(const QString &source, QString substituteUser(const QString &source,
const LanguageId &language) const; const LanguageId &language) const;
void processQueue();
Manager &manager_; Manager &manager_;
const Settings &settings_; const Settings &settings_;
QThread *workerThread_; QThread *workerThread_;
std::deque<TaskPtr> queue_;
}; };

View File

@ -13,11 +13,9 @@ void CorrectorWorker::handle(const TaskPtr &task)
SOFT_ASSERT(task->isValid(), return ); SOFT_ASSERT(task->isValid(), return );
SOFT_ASSERT(!hunspellDir_.isEmpty(), return ); SOFT_ASSERT(!hunspellDir_.isEmpty(), return );
LTRACE() << "Start hunspell correction" << task->sourceLanguage;
auto result = task; auto result = task;
if (!bundles_.count(task->sourceLanguage)) { if (!bundles_.count(task->sourceLanguage)) {
LTRACE() << "Create hunspell engine" << task->sourceLanguage;
auto engine = auto engine =
std::make_unique<HunspellCorrector>(task->sourceLanguage, hunspellDir_); std::make_unique<HunspellCorrector>(task->sourceLanguage, hunspellDir_);
@ -29,7 +27,6 @@ void CorrectorWorker::handle(const TaskPtr &task)
} }
bundles_.emplace(task->sourceLanguage, Bundle{std::move(engine), 0}); bundles_.emplace(task->sourceLanguage, Bundle{std::move(engine), 0});
LTRACE() << "Added hunspell engine" << task->sourceLanguage;
} }
auto &bundle = bundles_[task->sourceLanguage]; auto &bundle = bundles_[task->sourceLanguage];
@ -52,7 +49,6 @@ void CorrectorWorker::reset(const QString &hunspellDir)
hunspellDir_ = hunspellDir; hunspellDir_ = hunspellDir;
bundles_.clear(); bundles_.clear();
LTRACE() << "Cleared hunspell engines";
} }
void CorrectorWorker::removeUnused(Generation current) void CorrectorWorker::removeUnused(Generation current)
@ -64,9 +60,7 @@ void CorrectorWorker::removeUnused(Generation current)
if (it->second.usesLeft >= 0) { if (it->second.usesLeft >= 0) {
++it; ++it;
} else { } else {
const auto name = it->first;
it = bundles_.erase(it); it = bundles_.erase(it);
LTRACE() << "Removed unused hunspell engine" << name;
} }
} }
} }

View File

@ -93,12 +93,10 @@ void HunspellCorrector::init(const QString &path)
engine_ = engine_ =
std::make_unique<Hunspell>(qPrintable(aff), qPrintable(dics.first())); std::make_unique<Hunspell>(qPrintable(aff), qPrintable(dics.first()));
LTRACE() << "Created hunspell instance";
dics.pop_front(); dics.pop_front();
if (!dics.isEmpty()) { if (!dics.isEmpty()) {
for (const auto &dic : dics) engine_->add_dic(qPrintable(dic)); for (const auto &dic : dics) engine_->add_dic(qPrintable(dic));
LTRACE() << "Loaded hunspell dicts" << dics;
} }
} }

View File

@ -80,7 +80,6 @@ const std::unordered_map<LanguageId, LanguageCodes::Bundle>
{I("ita"), {I("ita"), S("it"), S("ita"), QT_TRANSLATE_NOOP("QObject", "Italian")}}, {I("ita"), {I("ita"), S("it"), S("ita"), QT_TRANSLATE_NOOP("QObject", "Italian")}},
// {I("iku"), {I("iku"), S("iu"), S("iku"), QT_TRANSLATE_NOOP("QObject", "Inuktitut")}}, // {I("iku"), {I("iku"), S("iu"), S("iku"), QT_TRANSLATE_NOOP("QObject", "Inuktitut")}},
{I("jpn"), {I("jpn"), S("ja"), S("jpn"), QT_TRANSLATE_NOOP("QObject", "Japanese")}}, {I("jpn"), {I("jpn"), S("ja"), S("jpn"), QT_TRANSLATE_NOOP("QObject", "Japanese")}},
{I("jpn_vert"), {I("jpn_vert"), S("ja"), S("jpn_vert"), QT_TRANSLATE_NOOP("QObject", "Japanese vertical")}},
{I("jav"), {I("jav"), S("jv"), S("jav"), QT_TRANSLATE_NOOP("QObject", "Javanese")}}, {I("jav"), {I("jav"), S("jv"), S("jav"), QT_TRANSLATE_NOOP("QObject", "Javanese")}},
// {I("kal"), {I("kal"), S("kl"), S("kal"), QT_TRANSLATE_NOOP("QObject", "Kalaallisut, Greenlandic")}}, // {I("kal"), {I("kal"), S("kl"), S("kal"), QT_TRANSLATE_NOOP("QObject", "Kalaallisut, Greenlandic")}},
{I("kan"), {I("kan"), S("kn"), S("kan"), QT_TRANSLATE_NOOP("QObject", "Kannada")}}, {I("kan"), {I("kan"), S("kn"), S("kan"), QT_TRANSLATE_NOOP("QObject", "Kannada")}},
@ -94,7 +93,6 @@ const std::unordered_map<LanguageId, LanguageCodes::Bundle>
// {I("kom"), {I("kom"), S("kv"), S("kom"), QT_TRANSLATE_NOOP("QObject", "Komi")}}, // {I("kom"), {I("kom"), S("kv"), S("kom"), QT_TRANSLATE_NOOP("QObject", "Komi")}},
// {I("kon"), {I("kon"), S("kg"), S("kon"), QT_TRANSLATE_NOOP("QObject", "Kongo")}}, // {I("kon"), {I("kon"), S("kg"), S("kon"), QT_TRANSLATE_NOOP("QObject", "Kongo")}},
{I("kor"), {I("kor"), S("ko"), S("kor"), QT_TRANSLATE_NOOP("QObject", "Korean")}}, {I("kor"), {I("kor"), S("ko"), S("kor"), QT_TRANSLATE_NOOP("QObject", "Korean")}},
{I("kor_vert"), {I("kor_vert"), S("ko"), S("kor_vert"), QT_TRANSLATE_NOOP("QObject", "Korean vertical")}},
{I("kur"), {I("kur"), S("ku"), S(""), QT_TRANSLATE_NOOP("QObject", "Kurdish")}}, {I("kur"), {I("kur"), S("ku"), S(""), QT_TRANSLATE_NOOP("QObject", "Kurdish")}},
// {I("kua"), {I("kua"), S("kj"), S("kua"), QT_TRANSLATE_NOOP("QObject", "Kuanyama, Kwanyama")}}, // {I("kua"), {I("kua"), S("kj"), S("kua"), QT_TRANSLATE_NOOP("QObject", "Kuanyama, Kwanyama")}},
{I("lat"), {I("lat"), S("la"), S("lat"), QT_TRANSLATE_NOOP("QObject", "Latin")}}, {I("lat"), {I("lat"), S("la"), S("lat"), QT_TRANSLATE_NOOP("QObject", "Latin")}},
@ -195,10 +193,8 @@ const std::unordered_map<LanguageId, LanguageCodes::Bundle>
{I("zul"), {I("zul"), S("zu"), S(""), QT_TRANSLATE_NOOP("QObject", "Zulu")}}, {I("zul"), {I("zul"), S("zu"), S(""), QT_TRANSLATE_NOOP("QObject", "Zulu")}},
// custom // custom
{I("chi_sim"), {I("chi_sim"), S("zh-CN"), S("chi_sim"), QT_TRANSLATE_NOOP("QObject", "Chinese (Simplified)")}}, {I("chi_sim"), {I("chi_sim"), S("zh-CN"), S("chi_sim"), QT_TRANSLATE_NOOP("QObject", "Chinese (Simplified)")}},
{I("chi_sim_vert"), {I("chi_sim_vert"), S("zh-CN"), S("chi_sim_vert"), QT_TRANSLATE_NOOP("QObject", "Chinese (Simplified) vertical")}},
{I("chi_tra"), {I("chi_tra"), S("zh-TW"), S("chi_tra"), QT_TRANSLATE_NOOP("QObject", "Chinese (Traditional)")}}, {I("chi_tra"), {I("chi_tra"), S("zh-TW"), S("chi_tra"), QT_TRANSLATE_NOOP("QObject", "Chinese (Traditional)")}},
{I("chi_tra_vert"), {I("chi_tra_vert"), S("zh-TW"), S("chi_tra_vert"), QT_TRANSLATE_NOOP("QObject", "Chinese (Traditional) vertical")}}, {I("fil"), {I("fil"), S(""), S("fil"), QT_TRANSLATE_NOOP("QObject", "Filipino")}},
{I("fil"), {I("fil"), S("tl"), S("fil"), QT_TRANSLATE_NOOP("QObject", "Filipino")}},
{I("chr"), {I("chr"), S(""), S("chr"), QT_TRANSLATE_NOOP("QObject", "Cherokee")}}, {I("chr"), {I("chr"), S(""), S("chr"), QT_TRANSLATE_NOOP("QObject", "Cherokee")}},
{I("ceb"), {I("ceb"), S(""), S("ceb"), QT_TRANSLATE_NOOP("QObject", "Cebuano")}}, {I("ceb"), {I("ceb"), S(""), S("ceb"), QT_TRANSLATE_NOOP("QObject", "Cebuano")}},
{I("syr"), {I("syr"), S(""), S("syr"), QT_TRANSLATE_NOOP("QObject", "Syriac")}}, {I("syr"), {I("syr"), S(""), S("syr"), QT_TRANSLATE_NOOP("QObject", "Syriac")}},

View File

@ -4,7 +4,6 @@
#include <optional> #include <optional>
#include <unordered_map> #include <unordered_map>
#include <vector>
using LanguageId = QString; using LanguageId = QString;

View File

@ -1,13 +1,10 @@
#include "apptranslator.h" #include "apptranslator.h"
#include "manager.h" #include "manager.h"
#include "singleapplication.h" #include "singleapplication.h"
#include "widgetstate.h"
#include <QApplication> #include <QApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <locale.h>
#define STR2(XXX) #XXX #define STR2(XXX) #XXX
#define STR(XXX) STR2(XXX) #define STR(XXX) STR2(XXX)
@ -30,7 +27,6 @@ int main(int argc, char *argv[])
parser.setApplicationDescription(QObject::tr("OCR and translation tool")); parser.setApplicationDescription(QObject::tr("OCR and translation tool"));
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
service::WidgetState::addHelp(parser);
parser.process(a); parser.process(a);
} }

View File

@ -5,7 +5,6 @@
#include "recognizer.h" #include "recognizer.h"
#include "representer.h" #include "representer.h"
#include "settingseditor.h" #include "settingseditor.h"
#include "settingsvalidator.h"
#include "task.h" #include "task.h"
#include "translator.h" #include "translator.h"
#include "trayicon.h" #include "trayicon.h"
@ -32,16 +31,17 @@ const auto resultHideWaitUs = 300'000;
using Loader = update::Loader; using Loader = update::Loader;
Manager::Manager() Manager::Manager()
: models_(std::make_unique<CommonModels>()) : settings_(std::make_unique<Settings>())
, settings_(std::make_unique<Settings>()) , updater_(std::make_unique<Loader>(Loader::Urls{{updatesUrl}}))
, updater_(std::make_unique<update::Updater>(QVector<QUrl>{{updatesUrl}})) , updateAutoChecker_(std::make_unique<update::AutoChecker>(*updater_))
, models_(std::make_unique<CommonModels>())
{ {
SOFT_ASSERT(settings_, return ); SOFT_ASSERT(settings_, return );
// updater components // updater components
(void)QT_TRANSLATE_NOOP("QObject", "app"); (void)QT_TRANSLATE_NOOP("QObject", "app");
(void)QT_TRANSLATE_NOOP("QObject", "recognizers"); (void)QT_TRANSLATE_NOOP("QObject", "recognizers");
(void)QT_TRANSLATE_NOOP("QObject", "correction"); (void)QT_TRANSLATE_NOOP("QObject", "hunspell");
(void)QT_TRANSLATE_NOOP("QObject", "translators"); (void)QT_TRANSLATE_NOOP("QObject", "translators");
tray_ = std::make_unique<TrayIcon>(*this, *settings_); tray_ = std::make_unique<TrayIcon>(*this, *settings_);
@ -61,9 +61,13 @@ Manager::Manager()
warnIfOutdated(); warnIfOutdated();
QObject::connect(updater_.get(), &update::Updater::error, // QObject::connect(updater_.get(), &update::Loader::error, //
tray_.get(), &TrayIcon::showError); tray_.get(), &TrayIcon::showError);
QObject::connect(updater_.get(), &update::Updater::updatesAvailable, // QObject::connect(updater_.get(), &update::Loader::updated, //
tray_.get(), [this] {
tray_->showInformation(QObject::tr("Update completed"));
});
QObject::connect(updater_.get(), &update::Loader::updatesAvailable, //
tray_.get(), [this] { tray_.get(), [this] {
tray_->showInformation(QObject::tr("Updates available")); tray_->showInformation(QObject::tr("Updates available"));
}); });
@ -72,13 +76,11 @@ Manager::Manager()
Manager::~Manager() Manager::~Manager()
{ {
SOFT_ASSERT(settings_, return ); SOFT_ASSERT(settings_, return );
SOFT_ASSERT(updater_, return ); if (updateAutoChecker_ && updateAutoChecker_->isLastCheckDateChanged()) {
if (updater_->lastUpdateCheck().isValid() && settings_->lastUpdateCheck = updateAutoChecker_->lastCheckDate();
settings_->lastUpdateCheck != updater_->lastUpdateCheck()) {
settings_->lastUpdateCheck = updater_->lastUpdateCheck();
settings_->saveLastUpdateCheck(); settings_->saveLastUpdateCheck();
LTRACE() << "saved last update time";
} }
setupTrace(false);
} }
void Manager::warnIfOutdated() void Manager::warnIfOutdated()
@ -101,15 +103,11 @@ void Manager::updateSettings()
{ {
LTRACE() << "updateSettings"; LTRACE() << "updateSettings";
SOFT_ASSERT(settings_, return ); SOFT_ASSERT(settings_, return );
setupTrace(settings_->writeTrace);
tray_->resetFatalError();
tray_->setTaskActionsEnabled(false);
settings_->writeTrace = setupTrace(settings_->writeTrace);
setupProxy(*settings_); setupProxy(*settings_);
setupUpdates(*settings_); setupUpdates(*settings_);
models_->update(settings_->tessdataPath, settings_->translatorsPath); models_->update(settings_->tessdataPath);
tray_->updateSettings(); tray_->updateSettings();
capturer_->updateSettings(); capturer_->updateSettings();
@ -119,14 +117,6 @@ void Manager::updateSettings()
representer_->updateSettings(); representer_->updateSettings();
tray_->setCaptureLockedEnabled(capturer_->canCaptureLocked()); tray_->setCaptureLockedEnabled(capturer_->canCaptureLocked());
SettingsValidator validator;
validator.correct(*settings_, *models_);
const auto errors = validator.check(*settings_, *models_);
if (errors.isEmpty())
return;
fatalError(QObject::tr("Incorrect settings found. Go to Settings"));
} }
void Manager::setupProxy(const Settings &settings) void Manager::setupProxy(const Settings &settings)
@ -156,18 +146,19 @@ void Manager::setupProxy(const Settings &settings)
void Manager::setupUpdates(const Settings &settings) void Manager::setupUpdates(const Settings &settings)
{ {
updater_->setExpansions({ updater_->model()->setExpansions({
{"$translators$", settings.translatorsPath}, {"$translators$", settings.translatorsDir},
{"$tessdata$", settings.tessdataPath}, {"$tessdata$", settings.tessdataPath},
{"$hunspell$", settings.hunspellPath}, {"$hunspell$", settings.hunspellDir},
{"$appdir$", QApplication::applicationDirPath()}, {"$appdir$", QApplication::applicationDirPath()},
}); });
updater_->setAutoUpdate(settings.autoUpdateIntervalDays, SOFT_ASSERT(updateAutoChecker_, return );
settings.lastUpdateCheck); updateAutoChecker_->setLastCheckDate(settings.lastUpdateCheck);
updateAutoChecker_->setCheckIntervalDays(settings.autoUpdateIntervalDays);
} }
bool Manager::setupTrace(bool isOn) void Manager::setupTrace(bool isOn)
{ {
const auto oldFile = debug::traceFileName(); const auto oldFile = debug::traceFileName();
@ -178,11 +169,11 @@ bool Manager::setupTrace(bool isOn)
if (!oldFile.isEmpty()) if (!oldFile.isEmpty())
QDesktopServices::openUrl(QUrl::fromLocalFile(oldFile)); QDesktopServices::openUrl(QUrl::fromLocalFile(oldFile));
return false; return;
} }
if (!oldFile.isEmpty()) if (!oldFile.isEmpty())
return true; return;
const auto traceFile = const auto traceFile =
QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QStandardPaths::writableLocation(QStandardPaths::TempLocation) +
@ -192,13 +183,12 @@ bool Manager::setupTrace(bool isOn)
if (!debug::setTraceFileName(traceFile)) { if (!debug::setTraceFileName(traceFile)) {
QMessageBox::warning( QMessageBox::warning(
nullptr, {}, QObject::tr("Failed to set log file: %1").arg(traceFile)); nullptr, {}, QObject::tr("Failed to set log file: %1").arg(traceFile));
return false; return;
} }
debug::isTrace = true; debug::isTrace = true;
QMessageBox::information( QMessageBox::information(
nullptr, {}, QObject::tr("Started logging to file: %1").arg(traceFile)); nullptr, {}, QObject::tr("Started logging to file: %1").arg(traceFile));
return true;
} }
void Manager::finishTask(const TaskPtr &task) void Manager::finishTask(const TaskPtr &task)

View File

@ -31,11 +31,10 @@ private:
void updateSettings(); void updateSettings();
void setupProxy(const Settings &settings); void setupProxy(const Settings &settings);
void setupUpdates(const Settings &settings); void setupUpdates(const Settings &settings);
bool setupTrace(bool isOn); void setupTrace(bool isOn);
void finishTask(const TaskPtr &task); void finishTask(const TaskPtr &task);
void warnIfOutdated(); void warnIfOutdated();
std::unique_ptr<CommonModels> models_;
std::unique_ptr<Settings> settings_; std::unique_ptr<Settings> settings_;
std::unique_ptr<TrayIcon> tray_; std::unique_ptr<TrayIcon> tray_;
std::unique_ptr<Capturer> capturer_; std::unique_ptr<Capturer> capturer_;
@ -43,6 +42,8 @@ private:
std::unique_ptr<Corrector> corrector_; std::unique_ptr<Corrector> corrector_;
std::unique_ptr<Translator> translator_; std::unique_ptr<Translator> translator_;
std::unique_ptr<Representer> representer_; std::unique_ptr<Representer> representer_;
std::unique_ptr<update::Updater> updater_; std::unique_ptr<update::Loader> updater_;
std::unique_ptr<update::AutoChecker> updateAutoChecker_;
std::unique_ptr<CommonModels> models_;
int activeTaskCount_{0}; int activeTaskCount_{0};
}; };

View File

@ -3,7 +3,6 @@
#include "manager.h" #include "manager.h"
#include "recognizerworker.h" #include "recognizerworker.h"
#include "settings.h" #include "settings.h"
#include "task.h"
#include "tesseract.h" #include "tesseract.h"
#include <QThread> #include <QThread>
@ -16,7 +15,7 @@ Recognizer::Recognizer(Manager &manager, const Settings &settings)
auto worker = new RecognizeWorker; auto worker = new RecognizeWorker;
connect(this, &Recognizer::reset, // connect(this, &Recognizer::reset, //
worker, &RecognizeWorker::reset); worker, &RecognizeWorker::reset);
connect(this, &Recognizer::recognizeImpl, // connect(this, &Recognizer::recognize, //
worker, &RecognizeWorker::handle); worker, &RecognizeWorker::handle);
connect(worker, &RecognizeWorker::finished, // connect(worker, &RecognizeWorker::finished, //
this, &Recognizer::recognized); this, &Recognizer::recognized);
@ -27,57 +26,22 @@ Recognizer::Recognizer(Manager &manager, const Settings &settings)
worker->moveToThread(workerThread_); worker->moveToThread(workerThread_);
} }
void Recognizer::recognize(const TaskPtr &task)
{
SOFT_ASSERT(task, return );
SOFT_ASSERT(task->isValid(), return );
if (task->sourceLanguage.isEmpty()) {
task->error = tr("No source language set. Check settings");
manager_.recognized(task);
return;
}
queue_.push_back(task);
if (queue_.size() == 1)
processQueue();
}
void Recognizer::processQueue()
{
if (queue_.empty())
return;
emit recognizeImpl(queue_.front());
}
void Recognizer::recognized(const TaskPtr &task) void Recognizer::recognized(const TaskPtr &task)
{ {
manager_.recognized(task); manager_.recognized(task);
SOFT_ASSERT(!queue_.empty(), return );
if (queue_.front() == task) {
queue_.pop_front();
} else {
LERROR() << "processed not first item in recognition queue";
queue_.clear();
}
processQueue();
} }
Recognizer::~Recognizer() Recognizer::~Recognizer()
{ {
workerThread_->quit(); workerThread_->quit();
const auto timeoutMs = 2000; const auto timeoutMs = 2000;
if (!workerThread_->wait(timeoutMs)) { if (!workerThread_->wait(timeoutMs))
LTRACE() << "terminating tesseract thread";
workerThread_->terminate(); workerThread_->terminate();
} }
}
void Recognizer::updateSettings() void Recognizer::updateSettings()
{ {
SOFT_ASSERT(!settings_.tessdataPath.isEmpty(), return ); SOFT_ASSERT(!settings_.tessdataPath.isEmpty(), return );
queue_.clear();
emit reset(settings_.tessdataPath); emit reset(settings_.tessdataPath);
} }

View File

@ -4,8 +4,6 @@
#include <QObject> #include <QObject>
#include <deque>
class Recognizer : public QObject class Recognizer : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -14,18 +12,15 @@ public:
~Recognizer(); ~Recognizer();
void updateSettings(); void updateSettings();
void recognize(const TaskPtr &task);
signals: signals:
void recognizeImpl(const TaskPtr &task); void recognize(const TaskPtr &task);
void reset(const QString &tessdataPath); void reset(const QString &tessdataPath);
private: private:
void recognized(const TaskPtr &task); void recognized(const TaskPtr &task);
void processQueue();
Manager &manager_; Manager &manager_;
const Settings &settings_; const Settings &settings_;
QThread *workerThread_; QThread *workerThread_;
std::deque<TaskPtr> queue_;
}; };

View File

@ -11,12 +11,9 @@ void RecognizeWorker::handle(const TaskPtr &task)
SOFT_ASSERT(task->isValid(), return ); SOFT_ASSERT(task->isValid(), return );
SOFT_ASSERT(!tessdataPath_.isEmpty(), return ); SOFT_ASSERT(!tessdataPath_.isEmpty(), return );
LTRACE() << "Start recognize" << task->captured;
auto result = task; auto result = task;
if (!engines_.count(task->sourceLanguage)) { if (!engines_.count(task->sourceLanguage)) {
LTRACE() << "Create OCR engine" << task->sourceLanguage;
auto engine = auto engine =
std::make_unique<Tesseract>(task->sourceLanguage, tessdataPath_); std::make_unique<Tesseract>(task->sourceLanguage, tessdataPath_);
@ -27,7 +24,6 @@ void RecognizeWorker::handle(const TaskPtr &task)
} }
engines_.emplace(task->sourceLanguage, std::move(engine)); engines_.emplace(task->sourceLanguage, std::move(engine));
LTRACE() << "Added OCR engine" << task->sourceLanguage;
} }
auto &engine = engines_[task->sourceLanguage]; auto &engine = engines_[task->sourceLanguage];
@ -50,7 +46,6 @@ void RecognizeWorker::reset(const QString &tessdataPath)
tessdataPath_ = tessdataPath; tessdataPath_ = tessdataPath;
engines_.clear(); engines_.clear();
LTRACE() << "Cleared OCR engines";
} }
void RecognizeWorker::removeUnused(Generation current) void RecognizeWorker::removeUnused(Generation current)
@ -63,7 +58,6 @@ void RecognizeWorker::removeUnused(Generation current)
continue; continue;
} }
engines_.erase(it->first); engines_.erase(it->first);
LTRACE() << "Removed unused OCR engine" << it->first;
it = lastGenerations_.erase(it); it = lastGenerations_.erase(it);
} }
} }

View File

@ -8,7 +8,6 @@
#include <QBuffer> #include <QBuffer>
#include <QDir> #include <QDir>
#include <QLibrary>
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
#include <fstream> #include <fstream>
@ -91,118 +90,33 @@ static double getScale(Pix *source)
return scale; return scale;
} }
// Smart pointer for Pix
class PixGuard
{
public:
explicit PixGuard(Pix *pix = nullptr)
: pix_(pix)
{
}
~PixGuard()
{
if (pix_)
pixDestroy(&pix_);
}
void operator=(Pix *pix)
{
if (!pix)
return;
if (pix_)
pixDestroy(&pix_);
pix_ = pix;
}
operator Pix *() { return pix_; }
Pix *operator->() { return pix_; }
Pix *&get() { return pix_; }
Pix *take()
{
auto ret = pix_;
pix_ = nullptr;
return ret;
}
void trace(const QString &name) const
{
LTRACE() << qPrintable(name) << pix_;
#if 0
if (!pix_)
return;
auto fileName = name + ".png";
fileName.replace(' ', "_");
convertImage(*pix_).save(fileName);
#endif
}
private:
Pix *pix_;
Q_DISABLE_COPY(PixGuard);
};
static Pix *prepareImage(const QImage &image) static Pix *prepareImage(const QImage &image)
{ {
auto pix = PixGuard(convertImage(image)); auto pix = convertImage(image);
SOFT_ASSERT(pix, return nullptr); SOFT_ASSERT(pix, return nullptr);
pix.trace("Pix 1 Converted");
auto gray = pixConvertRGBToGray(pix, 0.0, 0.0, 0.0);
SOFT_ASSERT(gray, return nullptr);
pixDestroy(&pix);
auto scaleSource = gray;
auto scaled = scaleSource;
if (const auto scale = getScale(scaleSource); scale > 1.0) {
scaled = pixScale(scaleSource, scale, scale);
if (!scaled)
scaled = scaleSource;
}
if (scaled != scaleSource)
pixDestroy(&scaleSource);
return scaled;
}
static void cleanupImage(Pix **image)
{ {
pix = pixConvertRGBToGray(pix, 0.0, 0.0, 0.0); pixDestroy(image);
pix.trace("Pix 2 Gray");
}
if (const auto scale = getScale(pix); scale > 1.0) {
pix = pixScaleGrayLI(pix, scale, scale);
pix.trace("Pix 3 Scaled");
}
l_int32 otsuSx = 5000;
l_int32 otsuSy = 5000;
l_int32 otsuSmoothx = 0;
l_int32 otsuSmoothy = 0;
l_float32 otsuScorefract = 0.1f;
{
PixGuard otsu;
pixOtsuAdaptiveThreshold(pix, otsuSx, otsuSy, otsuSmoothx, otsuSmoothy,
otsuScorefract, nullptr, &otsu.get());
pix.trace("Pix 4 Test Color Otsu");
// Get the average intensity of the border pixels,
// with average of 0.0 being completely white and 1.0 being completely black
// Top
auto avg = pixAverageOnLine(otsu, 0, 0, otsu->w - 1, 0, 1);
// Bottom
avg += pixAverageOnLine(otsu, 0, otsu->h - 1, otsu->w - 1, otsu->h - 1, 1);
// Left
avg += pixAverageOnLine(otsu, 0, 0, 0, otsu->h - 1, 1);
// Right
avg += pixAverageOnLine(otsu, otsu->w - 1, 0, otsu->w - 1, otsu->h - 1, 1);
avg /= 4.0f;
// If background is dark
l_float32 threshold = 0.5f;
if (avg > threshold) {
pix = pixInvert(nullptr, pix);
pix.trace("Pix 5 Inverted");
}
}
{
l_int32 usm_halfwidth = 5;
l_float32 usm_fract = 2.5f;
pix = pixUnsharpMaskingGray(pix, usm_halfwidth, usm_fract);
pix.trace("Pix 6 Unshapred");
}
{
pixOtsuAdaptiveThreshold(pix, otsuSx, otsuSy, otsuSmoothx, otsuSmoothy, 0.0,
nullptr, &pix.get());
pix.trace("Pix 7 Binarized");
}
pix.trace("Pix 8 Result");
return pix.take();
} }
Tesseract::Tesseract(const LanguageId &language, const QString &tessdataPath) Tesseract::Tesseract(const LanguageId &language, const QString &tessdataPath)
@ -217,23 +131,19 @@ Tesseract::~Tesseract() = default;
void Tesseract::init(const LanguageId &language, const QString &tessdataPath) void Tesseract::init(const LanguageId &language, const QString &tessdataPath)
{ {
SOFT_ASSERT(!api_, return ); SOFT_ASSERT(!engine_, return );
api_ = std::make_unique<tesseract::TessBaseAPI>(); engine_ = std::make_unique<tesseract::TessBaseAPI>();
LTRACE() << "Created Tesseract api" << api_.get();
const auto tesseractName = LanguageCodes::tesseract(language); const auto tesseractName = LanguageCodes::tesseract(language);
auto result = api_->Init(qPrintable(tessdataPath), qPrintable(tesseractName), auto result =
tesseract::OcrEngineMode::OEM_DEFAULT); engine_->Init(qPrintable(tessdataPath), qPrintable(tesseractName),
LTRACE() << "Inited Tesseract api" << result; tesseract::OEM_DEFAULT);
if (result == 0) if (result == 0)
return; return;
api_->SetPageSegMode(tesseract::PageSegMode::PSM_AUTO);
error_ = QObject::tr("init failed"); error_ = QObject::tr("init failed");
api_.reset(); engine_.reset();
LTRACE() << "Cleared Tesseract api";
} }
const QString &Tesseract::error() const const QString &Tesseract::error() const
@ -267,28 +177,20 @@ QStringList Tesseract::availableLanguageNames(const QString &path)
QString Tesseract::recognize(const QPixmap &source) QString Tesseract::recognize(const QPixmap &source)
{ {
SOFT_ASSERT(api_, return {}); SOFT_ASSERT(engine_, return {});
SOFT_ASSERT(!source.isNull(), return {}); SOFT_ASSERT(!source.isNull(), return {});
error_.clear(); error_.clear();
PixGuard image(prepareImage(source.toImage())); Pix *image = prepareImage(source.toImage());
SOFT_ASSERT(image, return {}); SOFT_ASSERT(image != NULL, return {});
LTRACE() << "Preprocessed Pix for OCR" << image; engine_->SetImage(image);
char *outText = engine_->GetUTF8Text();
api_->SetImage(image); engine_->Clear();
LTRACE() << "Set Pix to engine"; cleanupImage(&image);
const auto outText = api_->GetUTF8Text();
LTRACE() << "Received recognized text";
api_->Clear();
LTRACE() << "Cleared engine";
const auto result = QString(outText).trimmed();
QString result = QString(outText).trimmed();
delete[] outText; delete[] outText;
LTRACE() << "Cleared recognized text buffer";
if (result.isEmpty()) if (result.isEmpty())
error_ = QObject::tr("Failed to recognize text or no text selected"); error_ = QObject::tr("Failed to recognize text or no text selected");
@ -297,5 +199,5 @@ QString Tesseract::recognize(const QPixmap &source)
bool Tesseract::isValid() const bool Tesseract::isValid() const
{ {
return api_.get(); return engine_.get();
} }

View File

@ -7,11 +7,11 @@
#include <memory> #include <memory>
class QPixmap; class QPixmap;
class Task;
namespace tesseract namespace tesseract
{ {
class TessBaseAPI; class TessBaseAPI;
} }
class Task;
class Tesseract class Tesseract
{ {
@ -28,6 +28,6 @@ public:
private: private:
void init(const LanguageId& language, const QString& tessdataPath); void init(const LanguageId& language, const QString& tessdataPath);
std::unique_ptr<tesseract::TessBaseAPI> api_; std::unique_ptr<tesseract::TessBaseAPI> engine_;
QString error_; QString error_;
}; };

View File

@ -26,12 +26,6 @@ Representer::~Representer() = default;
void Representer::showLast() void Representer::showLast()
{ {
if (settings_.resultShowType == ResultMode::Tooltip) {
SOFT_ASSERT(lastTooltipTask_, return );
showTooltip(lastTooltipTask_);
return;
}
SOFT_ASSERT(!widgets_.empty(), return ); SOFT_ASSERT(!widgets_.empty(), return );
for (auto &widget : widgets_) { for (auto &widget : widgets_) {
SOFT_ASSERT(widget->task(), continue); SOFT_ASSERT(widget->task(), continue);
@ -44,12 +38,6 @@ void Representer::showLast()
void Representer::clipboardLast() void Representer::clipboardLast()
{ {
if (settings_.resultShowType == ResultMode::Tooltip) {
SOFT_ASSERT(lastTooltipTask_, return );
clipboardText(lastTooltipTask_);
return;
}
SOFT_ASSERT(!widgets_.empty(), return ); SOFT_ASSERT(!widgets_.empty(), return );
SOFT_ASSERT(widgets_.front()->task(), return ); SOFT_ASSERT(widgets_.front()->task(), return );
clipboardText(widgets_.front()->task()); clipboardText(widgets_.front()->task());
@ -82,7 +70,6 @@ void Representer::hide()
void Representer::updateSettings() void Representer::updateSettings()
{ {
lastTooltipTask_.reset();
if (widgets_.empty()) if (widgets_.empty())
return; return;
for (auto &w : widgets_) w->updateSettings(); for (auto &w : widgets_) w->updateSettings();
@ -135,28 +122,18 @@ bool Representer::eventFilter(QObject * /*watched*/, QEvent *event)
const auto casted = static_cast<QMouseEvent *>(event); const auto casted = static_cast<QMouseEvent *>(event);
if (casted->button() == Qt::LeftButton) if (casted->button() == Qt::LeftButton)
hide(); hide();
} else if (event->type() == QEvent::KeyPress) {
const auto casted = static_cast<QKeyEvent *>(event);
if (casted->key() == Qt::Key_Escape)
hide();
} }
return false; return false;
} }
void Representer::showTooltip(const TaskPtr &task) void Representer::showTooltip(const TaskPtr &task)
{ {
SOFT_ASSERT(task, return ); auto message = task->recognized + " - " + task->translated;
lastTooltipTask_ = task;
auto message = task->recognized;
if (!task->translated.isEmpty())
message += QLatin1String(" - ") + task->translated;
tray_.showInformation(message); tray_.showInformation(message);
} }
void Representer::showWidget(const TaskPtr &task) void Representer::showWidget(const TaskPtr &task)
{ {
SOFT_ASSERT(task, return );
generation_ = task->generation; generation_ = task->generation;
auto index = 0u; auto index = 0u;

View File

@ -40,5 +40,4 @@ private:
Generation generation_{}; Generation generation_{};
std::vector<std::unique_ptr<ResultWidget>> widgets_; std::vector<std::unique_ptr<ResultWidget>> widgets_;
std::unique_ptr<ResultEditor> editor_; std::unique_ptr<ResultEditor> editor_;
TaskPtr lastTooltipTask_;
}; };

View File

@ -63,8 +63,7 @@ ResultEditor::ResultEditor(Manager &manager, const CommonModels &models,
void ResultEditor::show(const TaskPtr &task) void ResultEditor::show(const TaskPtr &task)
{ {
SOFT_ASSERT(task, return ); SOFT_ASSERT(task, return );
task_ = std::make_shared<Task>(); task_ = task;
*task_ = *task;
image_->setPixmap(task->captured); image_->setPixmap(task->captured);
recognizedEdit_->setText(task->recognized); recognizedEdit_->setText(task->recognized);
@ -104,7 +103,6 @@ void ResultEditor::translate()
task_->targetLanguage = task_->targetLanguage =
LanguageCodes::idForName(targetLanguage_->currentText()); LanguageCodes::idForName(targetLanguage_->currentText());
task_->translators = settings_.translators; task_->translators = settings_.translators;
task_->corrected = recognizedEdit_->toPlainText();
manager_.corrected(task_); manager_.corrected(task_);
close(); close();
task_.reset(); task_.reset();

View File

@ -11,15 +11,13 @@
#include <QLabel> #include <QLabel>
#include <QMenu> #include <QMenu>
#include <QMouseEvent> #include <QMouseEvent>
#include <QScreen>
ResultWidget::ResultWidget(Manager &manager, Representer &representer, ResultWidget::ResultWidget(Manager &manager, Representer &representer,
const Settings &settings, QWidget *parent) const Settings &settings, QWidget *parent)
: QFrame(parent) : QFrame(parent)
, representer_(representer) , representer_(representer)
, settings_(settings) , settings_(settings)
, imagePlaceholder_(new QWidget(this)) , image_(new QLabel(this))
, image_(new QLabel(imagePlaceholder_))
, recognized_(new QLabel(this)) , recognized_(new QLabel(this))
, separator_(new QLabel(this)) , separator_(new QLabel(this))
, translated_(new QLabel(this)) , translated_(new QLabel(this))
@ -32,6 +30,8 @@ ResultWidget::ResultWidget(Manager &manager, Representer &representer,
setFrameShape(QFrame::StyledPanel); setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Plain); setFrameShadow(QFrame::Plain);
image_->setAlignment(Qt::AlignCenter);
recognized_->setAlignment(Qt::AlignCenter); recognized_->setAlignment(Qt::AlignCenter);
recognized_->setWordWrap(true); recognized_->setWordWrap(true);
@ -65,7 +65,7 @@ ResultWidget::ResultWidget(Manager &manager, Representer &representer,
installEventFilter(this); installEventFilter(this);
auto layout = new QVBoxLayout(this); auto layout = new QVBoxLayout(this);
layout->addWidget(imagePlaceholder_); layout->addWidget(image_);
layout->addWidget(recognized_); layout->addWidget(recognized_);
layout->addWidget(separator_); layout->addWidget(separator_);
layout->addWidget(translated_); layout->addWidget(translated_);
@ -86,8 +86,6 @@ void ResultWidget::show(const TaskPtr &task)
task_ = task; task_ = task;
image_->setPixmap(task->captured); image_->setPixmap(task->captured);
image_->resize(task->captured.size());
imagePlaceholder_->setMinimumSize(image_->size());
recognized_->setText(task->corrected); recognized_->setText(task->corrected);
const auto tooltip = task->recognized == task->corrected const auto tooltip = task->recognized == task->corrected
@ -108,37 +106,20 @@ void ResultWidget::show(const TaskPtr &task)
show(); show();
adjustSize(); adjustSize();
// window should not be smaller than selected image if (!image_->isVisible())
if (!imagePlaceholder_->isVisible())
resize(std::max(width(), task->captured.width()), resize(std::max(width(), task->captured.width()),
std::max(height(), task->captured.height())); std::max(height(), task->captured.height()));
// if window is wider than image then image should be at horizontal center QDesktopWidget *desktop = QApplication::desktop();
const auto correctionToCenterImage = Q_CHECK_PTR(desktop);
QPoint((width() - task->captured.width()) / 2, 2 * lineWidth()); const auto correction =
auto rect = QRect(task->capturePoint - correctionToCenterImage, size()); QPoint((width() - task->captured.width()) / 2, lineWidth());
auto rect = QRect(task->capturePoint - correction, size());
auto screen = QApplication::screenAt(task->capturePoint); const auto screenRect = desktop->screenGeometry(this);
SOFT_ASSERT(screen, return );
const auto screenRect = screen->geometry();
// window should not exceed horizontal borders
if (rect.right() > screenRect.right())
rect.moveRight(screenRect.right());
if (rect.left() < screenRect.left())
rect.moveLeft(screenRect.left());
// image should be where it was selected
if (imagePlaceholder_->isVisible()) {
const auto imageOffset =
task->capturePoint.x() - rect.left() - 2 * lineWidth();
image_->move(imageOffset, image_->y());
}
// window should not exceed vertical borders
const auto shouldTextOnTop = rect.bottom() > screenRect.bottom(); const auto shouldTextOnTop = rect.bottom() > screenRect.bottom();
if (shouldTextOnTop) if (shouldTextOnTop)
rect.moveBottom(rect.top() + task->captured.height() + 3 * lineWidth()); rect.moveBottom(rect.top() + task->captured.height() + lineWidth());
auto layout = static_cast<QBoxLayout *>(this->layout()); auto layout = static_cast<QBoxLayout *>(this->layout());
SOFT_ASSERT(layout, return ); SOFT_ASSERT(layout, return );
@ -175,7 +156,7 @@ void ResultWidget::updateSettings()
palette.setColor(QPalette::Window, separatorColor); palette.setColor(QPalette::Window, separatorColor);
separator_->setPalette(palette); separator_->setPalette(palette);
imagePlaceholder_->setVisible(settings_.showCaptured); image_->setVisible(settings_.showCaptured);
} }
void ResultWidget::mousePressEvent(QMouseEvent *event) void ResultWidget::mousePressEvent(QMouseEvent *event)

View File

@ -31,7 +31,6 @@ private:
Representer& representer_; Representer& representer_;
const Settings& settings_; const Settings& settings_;
TaskPtr task_; TaskPtr task_;
QWidget* imagePlaceholder_;
QLabel* image_; QLabel* image_;
QLabel* recognized_; QLabel* recognized_;
QLabel* separator_; QLabel* separator_;

View File

@ -6,19 +6,12 @@
#include <QMutex> #include <QMutex>
#include <QThread> #include <QThread>
#ifdef Q_OS_WIN
#include <io.h>
#else
#include <unistd.h>
#endif
namespace namespace
{ {
QtMessageHandler original = nullptr;
QMutex mutex; QMutex mutex;
QString fileName; QFile file;
int realStdout{}; QTextStream stream;
int realStderr{};
FILE *logFile{};
void handler(QtMsgType type, const QMessageLogContext &context, void handler(QtMsgType type, const QMessageLogContext &context,
const QString &msg) const QString &msg)
@ -36,33 +29,11 @@ void handler(QtMsgType type, const QMessageLogContext &context,
QFileInfo(context.file).fileName().toUtf8() + ':' + QFileInfo(context.file).fileName().toUtf8() + ':' +
QByteArray::number(context.line) + typeName + msg.toUtf8() + '\n'; QByteArray::number(context.line) + typeName + msg.toUtf8() + '\n';
SOFT_ASSERT(original, return );
original(type, context, msg);
QMutexLocker locker(&mutex); QMutexLocker locker(&mutex);
if (logFile) file.write(message);
write(fileno(logFile), message.data(), message.size());
if (realStderr > 0)
write(realStderr, message.data(), message.size());
}
void toDefaults()
{
qInstallMessageHandler(nullptr);
if (realStdout > 0) {
dup2(realStdout, fileno(stdout));
realStdout = -1;
}
if (realStderr > 0) {
dup2(realStderr, fileno(stderr));
realStderr = -1;
}
if (logFile) {
fclose(logFile);
logFile = nullptr;
}
fileName.clear();
} }
} // namespace } // namespace
@ -73,32 +44,28 @@ std::atomic_bool isTrace = false;
QString traceFileName() QString traceFileName()
{ {
QMutexLocker locker(&mutex); QMutexLocker locker(&mutex);
return fileName; return file.fileName();
} }
bool setTraceFileName(const QString &fileName) bool setTraceFileName(const QString &fileName)
{ {
QMutexLocker locker(&mutex); QMutexLocker locker(&mutex);
toDefaults(); original = nullptr;
qInstallMessageHandler(nullptr);
if (file.isOpen())
file.close();
if (fileName.isEmpty()) if (fileName.isEmpty())
return true; return true;
logFile = fopen(qPrintable(fileName), "w"); file.setFileName(fileName);
if (!logFile)
if (!file.open(QFile::WriteOnly | QFile::Unbuffered))
return false; return false;
realStdout = dup(fileno(stdout)); original = qInstallMessageHandler(handler);
realStderr = dup(fileno(stderr));
const auto fd = fileno(logFile);
dup2(fd, fileno(stdout));
dup2(fd, fileno(stderr));
::fileName = fileName;
qInstallMessageHandler(handler);
return true; return true;
} }

View File

@ -153,9 +153,6 @@ quint32 GlobalAction::nativeKeycode(Qt::Key key)
{ {
Display *display = QX11Info::display(); Display *display = QX11Info::display();
KeySym keySym = XStringToKeysym(qPrintable(QKeySequence(key).toString())); KeySym keySym = XStringToKeysym(qPrintable(QKeySequence(key).toString()));
if (XKeysymToString(keySym) == nullptr) {
keySym = QChar(key).unicode();
}
return XKeysymToKeycode(display, keySym); return XKeysymToKeycode(display, keySym);
} }
@ -226,9 +223,6 @@ quint32 GlobalAction::nativeKeycode(Qt::Key key)
case Qt::Key_Down: return VK_DOWN; case Qt::Key_Down: return VK_DOWN;
case Qt::Key_PageUp: return VK_PRIOR; case Qt::Key_PageUp: return VK_PRIOR;
case Qt::Key_PageDown: return VK_NEXT; case Qt::Key_PageDown: return VK_NEXT;
case Qt::Key_CapsLock: return VK_CAPITAL;
case Qt::Key_NumLock: return VK_NUMLOCK;
case Qt::Key_ScrollLock: return VK_SCROLL;
case Qt::Key_F1: return VK_F1; case Qt::Key_F1: return VK_F1;
case Qt::Key_F2: return VK_F2; case Qt::Key_F2: return VK_F2;
case Qt::Key_F3: return VK_F3; case Qt::Key_F3: return VK_F3;
@ -254,27 +248,11 @@ quint32 GlobalAction::nativeKeycode(Qt::Key key)
case Qt::Key_F23: return VK_F23; case Qt::Key_F23: return VK_F23;
case Qt::Key_F24: return VK_F24; case Qt::Key_F24: return VK_F24;
case Qt::Key_Space: return VK_SPACE; case Qt::Key_Space: return VK_SPACE;
case Qt::Key_QuoteDbl: return VK_OEM_7;
case Qt::Key_Apostrophe: return VK_OEM_7;
case Qt::Key_Period: return VK_DECIMAL;
case Qt::Key_Colon: return VK_OEM_1;
case Qt::Key_Semicolon: return VK_OEM_1;
case Qt::Key_Less: return VK_OEM_COMMA;
case Qt::Key_Greater: return VK_OEM_PERIOD;
case Qt::Key_Question: return VK_OEM_2;
case Qt::Key_BracketLeft: return VK_OEM_4;
case Qt::Key_Backslash: return VK_OEM_5;
case Qt::Key_BracketRight: return VK_OEM_6;
case Qt::Key_QuoteLeft: return VK_OEM_3;
case Qt::Key_BraceLeft: return VK_OEM_4;
case Qt::Key_Bar: return VK_OEM_5;
case Qt::Key_BraceRight: return VK_OEM_6;
case Qt::Key_Asterisk: return VK_MULTIPLY; case Qt::Key_Asterisk: return VK_MULTIPLY;
case Qt::Key_Plus: return VK_OEM_PLUS; case Qt::Key_Plus: return VK_ADD;
case Qt::Key_Comma: return VK_OEM_COMMA; case Qt::Key_Comma: return VK_SEPARATOR;
case Qt::Key_Minus: return VK_OEM_MINUS; case Qt::Key_Minus: return VK_SUBTRACT;
case Qt::Key_Slash: return VK_OEM_2; case Qt::Key_Slash: return VK_DIVIDE;
case Qt::Key_MediaNext: return VK_MEDIA_NEXT_TRACK; case Qt::Key_MediaNext: return VK_MEDIA_NEXT_TRACK;
case Qt::Key_MediaPrevious: return VK_MEDIA_PREV_TRACK; case Qt::Key_MediaPrevious: return VK_MEDIA_PREV_TRACK;
case Qt::Key_MediaPlay: return VK_MEDIA_PLAY_PAUSE; case Qt::Key_MediaPlay: return VK_MEDIA_PLAY_PAUSE;

View File

@ -1,71 +0,0 @@
#include "keysequenceedit.h"
#include <QKeyEvent>
namespace service
{
KeySequenceEdit::KeySequenceEdit(QWidget *parent)
: QLineEdit(parent)
{
setPlaceholderText(tr("Press shortcut"));
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_MacShowFocusRect, true);
setAttribute(Qt::WA_InputMethodEnabled, false);
}
const QKeySequence &KeySequenceEdit::keySequence() const
{
return current_;
}
void KeySequenceEdit::setKeySequence(const QKeySequence &newKeySequence)
{
setKeySequence(newKeySequence, true);
}
void KeySequenceEdit::setKeySequence(const QKeySequence &current,
bool updateFallback)
{
current_ = current;
if (updateFallback)
fallback_ = current;
setText(current_.toString(QKeySequence::NativeText));
}
bool KeySequenceEdit::event(QEvent *e)
{
switch (e->type()) {
case QEvent::Shortcut: return true;
case QEvent::ShortcutOverride: e->accept(); return true;
default: break;
}
return QWidget::event(e);
}
void KeySequenceEdit::keyPressEvent(QKeyEvent *event)
{
const auto key = event->key();
if (key == Qt::Key_Control || key == Qt::Key_Meta || key == Qt::Key_Alt ||
key == Qt::Key_Shift || key == Qt::Key_unknown) {
return;
}
if (event->key() == Qt::Key_Escape) {
setKeySequence(fallback_, false);
event->accept();
return;
}
if (event->key() == Qt::Key_Backspace) {
setKeySequence({}, false);
event->accept();
return;
}
QKeySequence seq = event->modifiers() + event->key();
setKeySequence(seq, false);
event->accept();
}
} // namespace service

View File

@ -1,25 +0,0 @@
#pragma once
#include <QLineEdit>
namespace service
{
class KeySequenceEdit : public QLineEdit
{
public:
KeySequenceEdit(QWidget *parent = nullptr);
const QKeySequence &keySequence() const;
void setKeySequence(const QKeySequence &newKeySequence);
bool event(QEvent *event) override;
protected:
void keyPressEvent(QKeyEvent *event) override;
private:
void setKeySequence(const QKeySequence &current, bool updateFallback);
QKeySequence current_;
QKeySequence fallback_;
};
} // namespace service

View File

@ -37,13 +37,12 @@ void RunAtSystemStart::setEnabled(bool isOn)
if (!f.open(QFile::WriteOnly)) if (!f.open(QFile::WriteOnly))
return; return;
const auto appPath =
qEnvironmentVariable("APPIMAGE", QCoreApplication::applicationFilePath());
const auto contents = QString(R"([Desktop Entry] const auto contents = QString(R"([Desktop Entry]
Name=%1 Name=%1
Exec=%2 Exec=%2
)") )")
.arg(QCoreApplication::applicationName(), appPath); .arg(QCoreApplication::applicationName(),
QCoreApplication::applicationFilePath());
f.write(contents.toUtf8()); f.write(contents.toUtf8());
} }
#endif #endif

View File

@ -20,8 +20,6 @@ namespace service
SingleApplication::SingleApplication(const QString &baseName) SingleApplication::SingleApplication(const QString &baseName)
: lockFile_(fileName(baseName)) : lockFile_(fileName(baseName))
{ {
lockFile_.setStaleLockTime(0);
if (!lockFile_.tryLock()) { if (!lockFile_.tryLock()) {
const auto lockName = fileName(baseName); const auto lockName = fileName(baseName);
LERROR() << QObject::tr("Another instance is running. Lock file is busy.") LERROR() << QObject::tr("Another instance is running. Lock file is busy.")

File diff suppressed because it is too large Load Diff

View File

@ -11,19 +11,20 @@ class QTreeView;
namespace update namespace update
{ {
enum class State { NotAvailable, NotInstalled, UpdateAvailable, Actual }; enum class State { NotAvailable, NotInstalled, UpdateAvailable, Actual };
enum class Action { Install, Remove }; enum class Action { NoAction, Remove, Install };
class Updater;
struct File { struct File {
QVector<QUrl> urls; QVector<QUrl> urls;
QString rawPath; QString rawPath;
QString expandedPath; QString expandedPath;
QString downloadPath;
QString md5; QString md5;
QDateTime versionDate; QDateTime versionDate;
int progress{0}; int progress{0};
}; };
using UserActions = std::multimap<Action, File>;
class UpdateDelegate : public QStyledItemDelegate class UpdateDelegate : public QStyledItemDelegate
{ {
Q_OBJECT Q_OBJECT
@ -37,17 +38,30 @@ class Model : public QAbstractItemModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum class Column { Name, State, Size, Version, Progress, Count }; enum class Column {
Name,
State,
Action,
Size,
Version,
Progress,
Files,
Count
};
explicit Model(Updater& updater); explicit Model(QObject* parent = nullptr);
void initView(QTreeView* view);
QString parse(const QByteArray& data); QString parse(const QByteArray& data);
void setExpansions(const QHash<QString, QString>& expansions); void setExpansions(const std::map<QString, QString>& expansions);
UserActions userActions() const;
void updateStates(); void updateStates();
bool hasUpdates() const; bool hasUpdates() const;
void updateProgress(const QUrl& url, int progress); void updateProgress(const QUrl& url, int progress);
void resetProgress();
void selectAllUpdates(); void selectAllUpdates();
void tryAction(Action action, const QModelIndex& index); void resetActions();
QModelIndex index(int row, int column, QModelIndex index(int row, int column,
const QModelIndex& parent) const override; const QModelIndex& parent) const override;
@ -58,13 +72,16 @@ public:
int role) const override; int role) const override;
QVariant data(const QModelIndex& index, int role) const override; QVariant data(const QModelIndex& index, int role) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override; Qt::ItemFlags flags(const QModelIndex& index) const override;
bool setData(const QModelIndex& index, const QVariant& value,
int role) override;
private: private:
struct Component { struct Component {
QString name; QString name;
State state{State::NotAvailable}; State state{State::NotAvailable};
Action action{Action::NoAction};
QString version; QString version;
QVector<File> files; std::vector<File> files;
bool checkOnly{false}; bool checkOnly{false};
std::vector<std::unique_ptr<Component>> children; std::vector<std::unique_ptr<Component>> children;
Component* parent{nullptr}; Component* parent{nullptr};
@ -74,14 +91,33 @@ private:
}; };
std::unique_ptr<Component> parse(const QJsonObject& json) const; std::unique_ptr<Component> parse(const QJsonObject& json) const;
void updateProgress(Component& component, const QUrl& url, int progress);
void updateState(Component& component);
State currentState(const File& file) const; State currentState(const File& file) const;
QString expanded(const QString& source) const; QString expanded(const QString& source) const;
Component* toComponent(const QModelIndex& index) const; Component* toComponent(const QModelIndex& index) const;
QModelIndex toIndex(const Component& component, int column) const; QModelIndex toIndex(const Component& component, int column) const;
Updater& updater_;
std::unique_ptr<Component> root_; std::unique_ptr<Component> root_;
QHash<QString, QString> expansions_; std::map<QString, QString> expansions_;
};
class Installer
{
public:
explicit Installer(const UserActions& actions);
bool commit();
QString errorString() const;
private:
void remove(const File& file);
void install(const File& file);
void checkRemove(const File& file);
void checkInstall(const File& file);
bool checkIsPossible();
UserActions actions_;
QStringList errors_;
}; };
class Loader : public QObject class Loader : public QObject
@ -89,85 +125,60 @@ class Loader : public QObject
Q_OBJECT Q_OBJECT
public: public:
using Urls = QVector<QUrl>; using Urls = QVector<QUrl>;
explicit Loader(Updater& updater); explicit Loader(const Urls& updateUrls, QObject* parent = nullptr);
void download(const Urls& urls); void checkForUpdates();
void applyUserActions();
Model* model() const;
signals:
void updatesAvailable();
void updated();
void error(const QString& error);
private: private:
void start(const Urls& urls, const QUrl& previous, const QString& error); void addError(const QString& text);
void dumpErrors();
void handleReply(QNetworkReply* reply); void handleReply(QNetworkReply* reply);
bool handleComponentReply(QNetworkReply* reply);
void handleUpdateReply(QNetworkReply* reply);
QString toError(QNetworkReply& reply) const;
void finishUpdate(const QString& error = {});
void commitUpdate();
void updateProgress(qint64 bytesSent, qint64 bytesTotal);
bool startDownload(File& file);
void startDownloadUpdates(const QUrl& previous);
Updater& updater_;
QNetworkAccessManager* network_; QNetworkAccessManager* network_;
QHash<QNetworkReply*, Urls> downloads_; Model* model_;
}; Urls updateUrls_;
QString downloadPath_;
class Installer std::map<QNetworkReply*, File*> downloads_;
{ QStringList errors_;
public: UserActions currentActions_;
void remove(const File& file);
void install(const File& file, const QByteArray& data);
void checkInstall(const File& file);
const QString& error() const;
private:
QString error_;
}; };
class AutoChecker : public QObject class AutoChecker : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
AutoChecker(Updater& updater, int intervalDays, const QDateTime& lastCheck); explicit AutoChecker(Loader& loader, QObject* parent = nullptr);
~AutoChecker(); ~AutoChecker();
const QDateTime& lastCheckDate() const; bool isLastCheckDateChanged() const;
QDateTime lastCheckDate() const;
void setCheckIntervalDays(int days);
void setLastCheckDate(const QDateTime& dt);
private: private:
void updateLastCheckDate(); void handleModelReset();
void scheduleNextCheck(); void scheduleNextCheck();
Updater& updater_; Loader& loader_;
bool isLastCheckDateChanged_{false};
int checkIntervalDays_{0}; int checkIntervalDays_{0};
QDateTime lastCheckDate_; QDateTime lastCheckDate_;
std::unique_ptr<QTimer> timer_; std::unique_ptr<QTimer> timer_;
}; };
class Updater : public QObject
{
Q_OBJECT
public:
explicit Updater(const QVector<QUrl>& updateUrls);
void initView(QTreeView* view);
void setExpansions(const QHash<QString, QString>& expansions);
void checkForUpdates();
QDateTime lastUpdateCheck() const;
void setAutoUpdate(int intervalDays, const QDateTime& lastCheck);
void applyAction(Action action, const QVector<File>& files);
void downloaded(const QUrl& url, const QByteArray& data);
void updateProgress(const QUrl& url, qint64 bytesSent, qint64 bytesTotal);
void downloadFailed(const QUrl& url, const QString& error);
signals:
void checkedForUpdates();
void updatesAvailable();
void updated();
void error(const QString& error);
private:
void handleModelDoubleClick(const QModelIndex& index);
void showModelContextMenu();
int findDownload(const QUrl& url) const;
QModelIndex fromProxy(const QModelIndex& index) const;
std::unique_ptr<Model> model_;
std::unique_ptr<Loader> loader_;
std::unique_ptr<AutoChecker> autoChecker_;
QVector<QUrl> updateUrls_;
QVector<File> downloading_;
};
} // namespace update } // namespace update

View File

@ -1,7 +1,6 @@
#include "widgetstate.h" #include "widgetstate.h"
#include "debug.h" #include "debug.h"
#include <QCommandLineParser>
#include <QCoreApplication> #include <QCoreApplication>
#include <QHeaderView> #include <QHeaderView>
#include <QMainWindow> #include <QMainWindow>
@ -104,13 +103,6 @@ bool WidgetState::eventFilter(QObject *watched, QEvent *event)
return QObject::eventFilter(watched, event); return QObject::eventFilter(watched, event);
} }
void WidgetState::addHelp(QCommandLineParser &parser)
{
parser.addOption(
{"reset-gui", QObject::tr("Do not restore user interface "
"(window size and position, etc)")});
}
void WidgetState::save(QWidget *widget) void WidgetState::save(QWidget *widget)
{ {
SOFT_ASSERT(widget, return ); SOFT_ASSERT(widget, return );

View File

@ -2,8 +2,6 @@
#include <QObject> #include <QObject>
class QCommandLineParser;
namespace service namespace service
{ {
class WidgetState : public QObject class WidgetState : public QObject
@ -13,8 +11,6 @@ public:
void add(QWidget *watched); void add(QWidget *watched);
bool eventFilter(QObject *watched, QEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override;
static void addHelp(QCommandLineParser &parser);
static void save(QWidget *widget); static void save(QWidget *widget);
static void restore(QWidget *widget); static void restore(QWidget *widget);
}; };

View File

@ -7,14 +7,9 @@
#include <QSettings> #include <QSettings>
#include <QStandardPaths> #include <QStandardPaths>
#include <array>
namespace namespace
{ {
const QString iniFileName() const QString iniFileName = "settings.ini";
{
return QApplication::applicationDirPath() + "/" + "settings.ini";
}
const QString qs_guiGroup = "GUI"; const QString qs_guiGroup = "GUI";
const QString qs_captureHotkey = "captureHotkey"; const QString qs_captureHotkey = "captureHotkey";
@ -101,7 +96,8 @@ Substitutions loadLegacySubstitutions()
const auto data = f.readAll(); const auto data = f.readAll();
const auto lines = QString::fromUtf8(data).split('\n', Qt::SkipEmptyParts); const auto lines =
QString::fromUtf8(data).split('\n', QString::SkipEmptyParts);
for (const auto& line : lines) { for (const auto& line : lines) {
const auto parts = line.mid(1, line.size() - 2).split("\",\""); // remove " const auto parts = line.mid(1, line.size() - 2).split("\",\""); // remove "
if (parts.size() < 3) if (parts.size() < 3)
@ -137,12 +133,11 @@ void cleanupOutdated(QSettings& settings)
void Settings::save() const void Settings::save() const
{ {
std::unique_ptr<QSettings> ptr; std::unique_ptr<QSettings> ptr;
const auto iniName = iniFileName();
if (isPortable_) { if (isPortable_) {
ptr = std::make_unique<QSettings>(iniName, QSettings::IniFormat); ptr = std::make_unique<QSettings>(iniFileName, QSettings::IniFormat);
} else { } else {
ptr = std::make_unique<QSettings>(); ptr = std::make_unique<QSettings>();
QFile::remove(iniName); QFile::remove(iniFileName);
} }
auto& settings = *ptr; auto& settings = *ptr;
@ -216,9 +211,8 @@ void Settings::save() const
void Settings::load() void Settings::load()
{ {
std::unique_ptr<QSettings> ptr; std::unique_ptr<QSettings> ptr;
const auto iniName = iniFileName(); if (QFile::exists(iniFileName)) {
if (QFile::exists(iniName)) { ptr = std::make_unique<QSettings>(iniFileName, QSettings::IniFormat);
ptr = std::make_unique<QSettings>(iniName, QSettings::IniFormat);
setPortable(true); setPortable(true);
} else { } else {
ptr = std::make_unique<QSettings>(); ptr = std::make_unique<QSettings>();
@ -310,9 +304,8 @@ void Settings::load()
void Settings::saveLastUpdateCheck() void Settings::saveLastUpdateCheck()
{ {
std::unique_ptr<QSettings> ptr; std::unique_ptr<QSettings> ptr;
const auto iniName = iniFileName(); if (QFile::exists(iniFileName)) {
if (QFile::exists(iniName)) { ptr = std::make_unique<QSettings>(iniFileName, QSettings::IniFormat);
ptr = std::make_unique<QSettings>(iniName, QSettings::IniFormat);
} else { } else {
ptr = std::make_unique<QSettings>(); ptr = std::make_unique<QSettings>();
} }
@ -333,11 +326,11 @@ void Settings::setPortable(bool isPortable)
isPortable_ = isPortable; isPortable_ = isPortable;
const auto baseDataPath = const auto baseDataPath =
(isPortable ? QApplication::applicationDirPath() (isPortable ? QDir().absolutePath()
: QStandardPaths::writableLocation( : QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation)) + QStandardPaths::AppDataLocation)) +
"/assets"; "/assets";
tessdataPath = baseDataPath + "/tessdata"; tessdataPath = baseDataPath + "/tessdata";
translatorsPath = baseDataPath + "/translators"; translatorsDir = baseDataPath + "/translators";
hunspellPath = baseDataPath + "/hunspell"; hunspellDir = baseDataPath + "/hunspell";
} }

View File

@ -7,7 +7,6 @@
#include <QStringList> #include <QStringList>
#include <chrono> #include <chrono>
#include <map>
enum class ResultMode { Widget, Tooltip }; enum class ResultMode { Widget, Tooltip };
@ -15,7 +14,7 @@ struct Substitution {
QString source; QString source;
QString target; QString target;
}; };
using Substitutions = std::multimap<LanguageId, Substitution>; using Substitutions = std::unordered_multimap<LanguageId, Substitution>;
enum class ProxyType { Disabled, System, Socks5, Http }; enum class ProxyType { Disabled, System, Socks5, Http };
@ -50,7 +49,7 @@ public:
QDateTime lastUpdateCheck; QDateTime lastUpdateCheck;
bool useHunspell{false}; bool useHunspell{false};
QString hunspellPath; QString hunspellDir;
Substitutions userSubstitutions; Substitutions userSubstitutions;
bool useUserSubstitutions{true}; bool useUserSubstitutions{true};
@ -58,12 +57,14 @@ public:
QString tessdataPath; QString tessdataPath;
QString sourceLanguage{"eng"}; QString sourceLanguage{"eng"};
LanguageIds availableOcrLanguages_;
bool doTranslation{true}; bool doTranslation{true};
bool ignoreSslErrors{false}; bool ignoreSslErrors{false};
bool forceRotateTranslators{false};
LanguageId targetLanguage{"rus"}; LanguageId targetLanguage{"rus"};
std::chrono::seconds translationTimeout{15}; std::chrono::seconds translationTimeout{15};
QString translatorsPath; QString translatorsDir;
QStringList translators{"google.js"}; QStringList translators{"google.js"};
ResultMode resultShowType{ResultMode::Widget}; // dialog ResultMode resultShowType{ResultMode::Widget}; // dialog

View File

@ -1,36 +1,18 @@
#include "settingseditor.h" #include "settingseditor.h"
#include "debug.h"
#include "languagecodes.h" #include "languagecodes.h"
#include "manager.h" #include "manager.h"
#include "runatsystemstart.h" #include "runatsystemstart.h"
#include "settingsvalidator.h" #include "translator.h"
#include "ui_settingseditor.h" #include "ui_settingseditor.h"
#include "updates.h" #include "updates.h"
#include "widgetstate.h" #include "widgetstate.h"
#include <QColorDialog> #include <QColorDialog>
#include <QStandardItemModel>
namespace SettingsEditor::SettingsEditor(Manager &manager, update::Loader &updater)
{
enum class Page { // order must match ui->pagesView
General,
Recognition,
Correction,
Translation,
Representation,
Update,
Help,
Count
};
enum class PageColumn { Name, Description, Error, Count };
} // namespace
SettingsEditor::SettingsEditor(Manager &manager, update::Updater &updater)
: ui(new Ui::SettingsEditor) : ui(new Ui::SettingsEditor)
, manager_(manager) , manager_(manager)
, updater_(updater) , updater_(updater)
, pageModel_(new QStandardItemModel(this))
{ {
ui->setupUi(this); ui->setupUi(this);
@ -38,70 +20,19 @@ SettingsEditor::SettingsEditor(Manager &manager, update::Updater &updater)
this, &SettingsEditor::handleButtonBoxClicked); this, &SettingsEditor::handleButtonBoxClicked);
connect(ui->portable, &QCheckBox::toggled, // connect(ui->portable, &QCheckBox::toggled, //
this, &SettingsEditor::updateState); this, &SettingsEditor::handlePortableChanged);
ui->runAtSystemStart->setEnabled(service::RunAtSystemStart::isAvailable()); ui->runAtSystemStart->setEnabled(service::RunAtSystemStart::isAvailable());
{ {
struct Info { auto model = new QStringListModel(this);
QString title; model->setStringList({tr("General"), tr("Recognition"), tr("Correction"),
QString description; tr("Translation"), tr("Representation"), tr("Update"),
}; tr("About")});
ui->pagesList->setModel(model);
QMap<Page, Info> names{
{Page::General,
{tr("General"), tr("This page contains general program settings")}},
{Page::Recognition,
{tr("Recognition"),
tr("This page contains text recognition settings. "
"It shows the available languages that program can convert from "
"image to text")}},
{Page::Correction,
{tr("Correction"),
tr("This page contains recognized text correction settings. "
"It allows to fix some errors after recognition.\n"
"Hunspell searches for words that are similar to recognized ones "
"in its dictionary.\n"
"User correction allows to manually fix some frequently "
"happening mistakes.\n"
"User correction occurs before hunspell correction if both "
"are enabled")}},
{Page::Translation,
{tr("Translation"),
tr("This page contains settings, related to translation of the "
"recognized text. "
"Translation is done via enabled (checked) translation services. "
"If one fails, then second one will be used and so on. "
"If translator hangs it will be treated as failed after "
"given timeout")}},
{Page::Representation,
{tr("Representation"),
tr("This page contains result representation settings")}},
{Page::Update,
{tr("Update"),
tr("This page allow to install/update/remove program resources")}},
{Page::Help, {tr("Help"), tr("")}},
};
for (const auto &i : names) {
const auto error = QString();
pageModel_->appendRow({new QStandardItem(i.title),
new QStandardItem(i.description),
new QStandardItem(error)});
}
ui->pagesList->setModel(pageModel_);
ui->pagesList->setModelColumn(int(PageColumn::Name));
auto selection = ui->pagesList->selectionModel(); auto selection = ui->pagesList->selectionModel();
connect(selection, &QItemSelectionModel::currentRowChanged, // connect(selection, &QItemSelectionModel::currentRowChanged, //
this, &SettingsEditor::updateCurrentPage); this, &SettingsEditor::updateCurrentPage);
selection->select(pageModel_->index(0, 0),
QItemSelectionModel::SelectCurrent);
} }
{ {
@ -120,7 +51,7 @@ SettingsEditor::SettingsEditor(Manager &manager, update::Updater &updater)
ui->proxyPassEdit->setEchoMode(QLineEdit::PasswordEchoOnEdit); ui->proxyPassEdit->setEchoMode(QLineEdit::PasswordEchoOnEdit);
} }
// recognition // translation
ui->tesseractLangCombo->setModel(models_.sourceLanguageModel()); ui->tesseractLangCombo->setModel(models_.sourceLanguageModel());
// correction // correction
@ -141,12 +72,6 @@ SettingsEditor::SettingsEditor(Manager &manager, update::Updater &updater)
ui->fontColor->setAutoFillBackground(true); ui->fontColor->setAutoFillBackground(true);
ui->backgroundColor->setAutoFillBackground(true); ui->backgroundColor->setAutoFillBackground(true);
ui->backgroundColor->setText(tr("Sample text")); ui->backgroundColor->setText(tr("Sample text"));
ui->fontColor->setFocusPolicy(Qt::FocusPolicy::NoFocus);
ui->backgroundColor->setFocusPolicy(Qt::FocusPolicy::NoFocus);
#ifdef Q_OS_WINDOWS
ui->fontColor->setFlat(true);
ui->backgroundColor->setFlat(true);
#endif
connect(ui->dialogRadio, &QRadioButton::toggled, // connect(ui->dialogRadio, &QRadioButton::toggled, //
ui->resultWindow, &QTableWidget::setEnabled); ui->resultWindow, &QTableWidget::setEnabled);
connect(ui->resultFont, &QFontComboBox::currentFontChanged, // connect(ui->resultFont, &QFontComboBox::currentFontChanged, //
@ -154,75 +79,38 @@ SettingsEditor::SettingsEditor(Manager &manager, update::Updater &updater)
connect(ui->resultFontSize, qOverload<int>(&QSpinBox::valueChanged), // connect(ui->resultFontSize, qOverload<int>(&QSpinBox::valueChanged), //
this, &SettingsEditor::updateResultFont); this, &SettingsEditor::updateResultFont);
connect(ui->fontColor, &QPushButton::clicked, // connect(ui->fontColor, &QPushButton::clicked, //
this, [this] { this, [this] { pickColor(ColorContext::Font); });
pickColor(ui->fontColor);
updateResultFont();
});
connect(ui->backgroundColor, &QPushButton::clicked, // connect(ui->backgroundColor, &QPushButton::clicked, //
this, [this] { this, [this] { pickColor(ColorContext::Bagkround); });
pickColor(ui->backgroundColor);
updateResultFont();
});
// updates // updates
ui->updatesView->header()->setObjectName("updatesHeader"); updater.model()->initView(ui->updatesView);
updater_.initView(ui->updatesView); adjustUpdatesView();
connect(&updater_, &update::Updater::updated, // connect(updater_.model(), &QAbstractItemModel::modelReset, //
this, &SettingsEditor::updateState); this, &SettingsEditor::adjustUpdatesView);
connect(&updater_, &update::Loader::updated, //
this, &SettingsEditor::adjustUpdatesView);
connect(ui->checkUpdates, &QPushButton::clicked, // connect(ui->checkUpdates, &QPushButton::clicked, //
&updater_, &update::Updater::checkForUpdates); &updater_, &update::Loader::checkForUpdates);
connect(ui->applyUpdates, &QPushButton::clicked, //
&updater_, &update::Loader::applyUserActions);
// about // about
{ {
const auto mail = "translator@gres.biz"; const auto mail = "translator@gres.biz";
const QString baseUrl = "https://github.com/OneMoreGres/ScreenTranslator"; const auto issues =
const auto issues = baseUrl + "/issues"; "https://github.com/OneMoreGres/ScreenTranslator/issues";
QLocale locale; const auto aboutText =
const auto changelog =
baseUrl + "/blob/master/share/Changelog_" +
(locale.language() == QLocale::Russian ? "ru" : "en") + ".md";
const auto license = baseUrl + "/blob/master/LICENSE.md";
const auto help = locale.language() == QLocale::Russian
? "https://translator.gres.biz/page/download/"
: baseUrl + "/blob/master/README.md";
const auto aboutLines = QStringList{
QObject::tr( QObject::tr(
R"(<p>Optical character recognition (OCR) and translation tool</p>)"), R"(<p>Optical character recognition (OCR) and translation tool</p>
QObject::tr(R"(<p>Version: %1</p>)") <p>Version: %1</p>
.arg(QApplication::applicationVersion()), <p>Author: Gres (<a href="mailto:%2">%2</a>)</p>
QObject::tr(R"(<p>Setup instructions: <a href="%1">%1</a></p>)") <p>Issues: <a href="%3">%3</a></p>)")
.arg(help), .arg(QApplication::applicationVersion(), mail, issues);
QObject::tr(R"(<p>Changelog: <a href="%1">%2</a></p>)")
.arg(changelog, QUrl(changelog).fileName()),
QObject::tr(R"(<p>License: <a href="%3">MIT</a></p>)").arg(license),
QObject::tr(R"(<p>Author: Gres (<a href="mailto:%1">%1</a>)</p>)")
.arg(mail),
QObject::tr(R"(<p>Issues: <a href="%1">%1</a></p>)").arg(issues),
};
ui->aboutLabel->setText(aboutLines.join('\n')); ui->aboutLabel->setText(aboutText);
ui->aboutLabel->setTextFormat(Qt::RichText); ui->aboutLabel->setTextFormat(Qt::RichText);
ui->aboutLabel->setOpenExternalLinks(true); ui->aboutLabel->setOpenExternalLinks(true);
ui->helpLabel->setText(
tr("The program workflow consists of the following steps:\n"
"1. Selection on the screen area\n"
"2. Recognition of the selected area\n"
"3. Correction of the recognized text (optional)\n"
"4. Translation of the corrected text (optional)\n"
"User interaction is only required for step 1.\n"
"Steps 2, 3 and 4 require additional data that can be "
"downloaded from "
"the updates page.\n"
"\n"
"At first start, go to the updates page and install desired "
"recognition languages and translators and, optionally, "
"hunspell "
"dictionaries.\n"
"Then set default recognition and translation languages, "
"enable some "
"(or all) translators and the \"translate text\" setting, "
"if needed."));
} }
new service::WidgetState(this); new service::WidgetState(this);
@ -271,7 +159,13 @@ Settings SettingsEditor::settings() const
std::chrono::seconds(ui->translateTimeoutSpin->value()); std::chrono::seconds(ui->translateTimeoutSpin->value());
settings.targetLanguage = settings.targetLanguage =
LanguageCodes::idForName(ui->translateLangCombo->currentText()); LanguageCodes::idForName(ui->translateLangCombo->currentText());
settings.translators = enabledTranslators();
settings.translators.clear();
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
auto item = ui->translatorList->item(i);
if (item->checkState() == Qt::Checked)
settings.translators.append(item->text());
}
settings.resultShowType = settings.resultShowType =
ui->trayRadio->isChecked() ? ResultMode::Tooltip : ResultMode::Widget; ui->trayRadio->isChecked() ? ResultMode::Tooltip : ResultMode::Widget;
@ -290,10 +184,11 @@ Settings SettingsEditor::settings() const
void SettingsEditor::setSettings(const Settings &settings) void SettingsEditor::setSettings(const Settings &settings)
{ {
if (settings.isPortable() == ui->portable->isChecked())
updateModels(settings.tessdataPath);
wasPortable_ = settings.isPortable(); wasPortable_ = settings.isPortable();
ui->portable->blockSignals(true);
ui->portable->setChecked(settings.isPortable()); ui->portable->setChecked(settings.isPortable());
ui->portable->blockSignals(false);
ui->runAtSystemStart->setChecked(settings.runAtSystemStart); ui->runAtSystemStart->setChecked(settings.runAtSystemStart);
@ -314,23 +209,22 @@ void SettingsEditor::setSettings(const Settings &settings)
ui->proxySaveCheck->setChecked(settings.proxySavePassword); ui->proxySaveCheck->setChecked(settings.proxySavePassword);
ui->tessdataPath->setText(settings.tessdataPath); ui->tessdataPath->setText(settings.tessdataPath);
ui->translatorsPath->setText(settings.translatorsPath);
updateModels();
ui->tesseractLangCombo->setCurrentText( ui->tesseractLangCombo->setCurrentText(
LanguageCodes::name(settings.sourceLanguage)); LanguageCodes::name(settings.sourceLanguage));
ui->useHunspell->setChecked(settings.useHunspell); ui->useHunspell->setChecked(settings.useHunspell);
ui->hunspellDir->setText(settings.hunspellPath); ui->hunspellDir->setText(settings.hunspellDir);
ui->useUserSubstitutions->setChecked(settings.useUserSubstitutions); ui->useUserSubstitutions->setChecked(settings.useUserSubstitutions);
ui->userSubstitutionsTable->setSubstitutions(settings.userSubstitutions); ui->userSubstitutionsTable->setSubstitutions(settings.userSubstitutions);
ui->doTranslationCheck->setChecked(settings.doTranslation); ui->doTranslationCheck->setChecked(settings.doTranslation);
ui->ignoreSslCheck->setChecked(settings.ignoreSslErrors); ui->ignoreSslCheck->setChecked(settings.ignoreSslErrors);
ui->translateTimeoutSpin->setValue(settings.translationTimeout.count()); ui->translateTimeoutSpin->setValue(settings.translationTimeout.count());
ui->translatorsPath->setText(settings.translatorsDir);
enabledTranslators_ = settings.translators;
updateTranslators();
ui->translateLangCombo->setCurrentText( ui->translateLangCombo->setCurrentText(
LanguageCodes::name(settings.targetLanguage)); LanguageCodes::name(settings.targetLanguage));
updateTranslators(settings.translators);
ui->trayRadio->setChecked(settings.resultShowType == ResultMode::Tooltip); ui->trayRadio->setChecked(settings.resultShowType == ResultMode::Tooltip);
ui->dialogRadio->setChecked(settings.resultShowType == ResultMode::Widget); ui->dialogRadio->setChecked(settings.resultShowType == ResultMode::Widget);
@ -348,73 +242,49 @@ void SettingsEditor::setSettings(const Settings &settings)
ui->showCaptured->setChecked(settings.showCaptured); ui->showCaptured->setChecked(settings.showCaptured);
ui->autoUpdateInterval->setValue(settings.autoUpdateIntervalDays); ui->autoUpdateInterval->setValue(settings.autoUpdateIntervalDays);
updateState();
}
void SettingsEditor::updateState()
{
Settings settings;
settings.setPortable(ui->portable->isChecked());
ui->tessdataPath->setText(settings.tessdataPath);
ui->translatorsPath->setText(settings.translatorsPath);
ui->hunspellDir->setText(settings.hunspellPath);
updateModels();
updateTranslators(enabledTranslators());
validateSettings();
updateCurrentPage();
const auto portableChanged = wasPortable_ != settings.isPortable();
ui->pageUpdate->setEnabled(!portableChanged);
ui->pageUpdate->setToolTip(portableChanged
? tr("Portable changed. Apply settings first")
: QString());
} }
void SettingsEditor::updateCurrentPage() void SettingsEditor::updateCurrentPage()
{ {
const auto row = ui->pagesList->currentIndex().row(); ui->pagesView->setCurrentIndex(ui->pagesList->currentIndex().row());
const auto description = pageModel_->index(row, int(PageColumn::Description));
ui->pageInfoLabel->setText(description.data().toString());
ui->pageInfoLabel->setVisible(!ui->pageInfoLabel->text().isEmpty());
const auto error = pageModel_->index(row, int(PageColumn::Error));
ui->pageErrorLabel->setText(error.data().toString());
ui->pageErrorLabel->setVisible(!ui->pageErrorLabel->text().isEmpty());
ui->pagesView->setCurrentIndex(row);
if (ui->pagesView->currentWidget() != ui->pageUpdate)
return;
if (ui->updatesView->model()->rowCount() == 0)
updater_.checkForUpdates();
} }
void SettingsEditor::updateTranslators(const QStringList &translators) void SettingsEditor::updateTranslators()
{ {
ui->translatorList->clear(); ui->translatorList->clear();
if (models_.translators().isEmpty())
auto names = Translator::availableTranslators(ui->translatorsPath->text());
if (names.isEmpty())
return; return;
QStringList all; std::sort(names.begin(), names.end());
for (const auto &i : translators) {
if (models_.translators().contains(i)) if (!enabledTranslators_.isEmpty()) {
all.append(i); for (const auto &name : enabledTranslators_) names.removeOne(name);
names = enabledTranslators_ + names;
} }
all += models_.translators();
all.removeDuplicates(); ui->translatorList->addItems(names);
ui->translatorList->addItems(all);
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) { for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
auto item = ui->translatorList->item(i); auto item = ui->translatorList->item(i);
item->setCheckState(translators.contains(item->text()) ? Qt::Checked item->setCheckState(enabledTranslators_.contains(item->text())
? Qt::Checked
: Qt::Unchecked); : Qt::Unchecked);
} }
} }
void SettingsEditor::adjustUpdatesView()
{
ui->updatesView->resizeColumnToContents(int(update::Model::Column::Name));
if (ui->tessdataPath->text().isEmpty()) // not inited yet
return;
updateModels(ui->tessdataPath->text());
updateTranslators();
}
void SettingsEditor::handleButtonBoxClicked(QAbstractButton *button) void SettingsEditor::handleButtonBoxClicked(QAbstractButton *button)
{ {
if (!button) if (!button)
@ -431,48 +301,49 @@ void SettingsEditor::handleButtonBoxClicked(QAbstractButton *button)
if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) { if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) {
const auto settings = this->settings(); const auto settings = this->settings();
manager_.applySettings(settings); manager_.applySettings(settings);
wasPortable_ = ui->portable->isChecked(); if (settings.isPortable() != wasPortable_) {
updateState(); wasPortable_ = settings.isPortable();
handlePortableChanged();
}
return; return;
} }
} }
void SettingsEditor::handlePortableChanged()
{
Settings settings;
settings.setPortable(ui->portable->isChecked());
ui->tessdataPath->setText(settings.tessdataPath);
ui->translatorsPath->setText(settings.translatorsDir);
ui->hunspellDir->setText(settings.hunspellDir);
updateModels(settings.tessdataPath);
updateTranslators();
const auto portableChanged = wasPortable_ != settings.isPortable();
ui->pageUpdate->setEnabled(!portableChanged);
ui->pageUpdate->setToolTip(portableChanged
? tr("Portable changed. Apply settings first")
: QString());
}
void SettingsEditor::updateResultFont() void SettingsEditor::updateResultFont()
{ {
auto font = ui->resultFont->currentFont(); auto font = ui->resultFont->currentFont();
font.setPointSize(ui->resultFontSize->value()); font.setPointSize(ui->resultFontSize->value());
ui->backgroundColor->setFont(font); ui->resultFont->setFont(font);
auto fontColor = ui->fontColor->palette().color(QPalette::Button);
QPalette palette(ui->backgroundColor->palette());
palette.setColor(QPalette::ButtonText, fontColor);
ui->backgroundColor->setPalette(palette);
} }
QStringList SettingsEditor::enabledTranslators() const void SettingsEditor::updateModels(const QString &tessdataPath)
{
QStringList result;
for (auto i = 0, end = ui->translatorList->count(); i < end; ++i) {
auto item = ui->translatorList->item(i);
if (item->checkState() == Qt::Checked)
result.append(item->text());
}
return result;
}
void SettingsEditor::updateModels()
{ {
const auto source = ui->tesseractLangCombo->currentText(); const auto source = ui->tesseractLangCombo->currentText();
models_.update(ui->tessdataPath->text(), ui->translatorsPath->text()); models_.update(tessdataPath);
if (!source.isEmpty()) {
ui->tesseractLangCombo->setCurrentText(source); ui->tesseractLangCombo->setCurrentText(source);
} else if (ui->tesseractLangCombo->count() > 0) {
ui->tesseractLangCombo->setCurrentIndex(0);
}
} }
void SettingsEditor::pickColor(QWidget *widget) void SettingsEditor::pickColor(ColorContext context)
{ {
const auto widget =
context == ColorContext::Font ? ui->fontColor : ui->backgroundColor;
const auto original = widget->palette().color(QPalette::Button); const auto original = widget->palette().color(QPalette::Button);
const auto color = QColorDialog::getColor(original, this); const auto color = QColorDialog::getColor(original, this);
@ -482,53 +353,12 @@ void SettingsEditor::pickColor(QWidget *widget)
QPalette palette(widget->palette()); QPalette palette(widget->palette());
palette.setColor(QPalette::Button, color); palette.setColor(QPalette::Button, color);
widget->setPalette(palette); widget->setPalette(palette);
}
void SettingsEditor::validateSettings() if (context == ColorContext::Bagkround)
{
SettingsValidator validator;
{
auto settings = this->settings();
if (validator.correct(settings, models_)) {
setSettings(settings);
return;
}
}
for (auto i = 0, end = pageModel_->rowCount(); i < end; ++i) {
const auto name = pageModel_->index(i, int(PageColumn::Name));
pageModel_->setData(name, QBrush(Qt::black), Qt::ForegroundRole);
const auto error = pageModel_->index(i, int(PageColumn::Error));
pageModel_->setData(error, {});
}
const auto errors = validator.check(settings(), models_);
if (errors.isEmpty())
return; return;
using E = SettingsValidator::Error; palette = ui->backgroundColor->palette();
QMap<E, Page> errorToPage{ palette.setColor(QPalette::ButtonText, color);
{E::NoSourceInstalled, Page::Update}, ui->backgroundColor->setPalette(palette);
{E::NoSourceSet, Page::Recognition}, ui->backgroundColor->update();
{E::NoTranslatorInstalled, Page::Update},
{E::NoTranslatorSet, Page::Translation},
{E::NoTargetSet, Page::Translation},
};
QMap<Page, QStringList> summary;
for (const auto err : errors) {
SOFT_ASSERT(errorToPage.contains(err), continue);
auto page = errorToPage[err];
summary[page].push_back(validator.toString(err));
}
for (auto it = summary.cbegin(), end = summary.cend(); it != end; ++it) {
const auto row = int(it.key());
const auto index = pageModel_->index(row, int(PageColumn::Name));
pageModel_->setData(index, QBrush(Qt::red), Qt::ForegroundRole);
const auto error = pageModel_->index(row, int(PageColumn::Error));
pageModel_->setData(error, it.value().join('\n'));
}
} }

View File

@ -10,35 +10,33 @@ namespace Ui
class SettingsEditor; class SettingsEditor;
} }
class QAbstractButton; class QAbstractButton;
class QStandardItemModel;
class SettingsEditor : public QDialog class SettingsEditor : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
SettingsEditor(Manager &manager, update::Updater &updater); SettingsEditor(Manager &manager, update::Loader &updater);
~SettingsEditor(); ~SettingsEditor();
Settings settings() const; Settings settings() const;
void setSettings(const Settings &settings); void setSettings(const Settings &settings);
private: private:
void handleButtonBoxClicked(QAbstractButton *button); enum ColorContext { Font, Bagkround };
void pickColor(QWidget *widget);
void updateResultFont();
QStringList enabledTranslators() const;
void updateState();
void updateCurrentPage(); void updateCurrentPage();
void updateTranslators(const QStringList &translators); void updateTranslators();
void updateModels(); void adjustUpdatesView();
void validateSettings(); void handleButtonBoxClicked(QAbstractButton *button);
void handlePortableChanged();
void updateResultFont();
void updateModels(const QString &tessdataPath);
void pickColor(ColorContext context);
Ui::SettingsEditor *ui; Ui::SettingsEditor *ui;
Manager &manager_; Manager &manager_;
update::Updater &updater_; update::Loader &updater_;
CommonModels models_; CommonModels models_;
QStringList enabledTranslators_;
bool wasPortable_{false}; bool wasPortable_{false};
QStandardItemModel *pageModel_;
}; };

View File

@ -7,13 +7,23 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>889</width> <width>889</width>
<height>611</height> <height>549</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Settings</string> <string>Settings</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_6"> <layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="0" colspan="4">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QListView" name="pagesList"> <widget class="QListView" name="pagesList">
<property name="sizePolicy"> <property name="sizePolicy">
@ -27,81 +37,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1" colspan="3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="pageInfoLabel">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="pageErrorLabel">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>118</red>
<green>118</green>
<blue>118</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="pagesView"> <widget class="QStackedWidget" name="pagesView">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@ -114,7 +50,66 @@
</property> </property>
<widget class="QWidget" name="pageGeneral"> <widget class="QWidget" name="pageGeneral">
<layout class="QGridLayout" name="gridLayout_11"> <layout class="QGridLayout" name="gridLayout_11">
<item row="2" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Shortcuts</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="1">
<widget class="QKeySequenceEdit" name="clipboardEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Repeat capture</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QKeySequenceEdit" name="repeatCaptureEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Show last result</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QKeySequenceEdit" name="captureEdit"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Copy result to clipboard</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QKeySequenceEdit" name="repeatEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Capture</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Capture saved areas</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QKeySequenceEdit" name="captureLockedEdit"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
<property name="title"> <property name="title">
<string>Proxy</string> <string>Proxy</string>
@ -187,7 +182,28 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="2" column="0">
<widget class="QCheckBox" name="showOnStart">
<property name="text">
<string>Show message on program start</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="runAtSystemStart">
<property name="text">
<string>Run at system start</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="portable">
<property name="text">
<string>Portable (store data in same folder)</string>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer_4"> <spacer name="verticalSpacer_4">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
@ -200,97 +216,33 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="4" column="1"> <item row="3" column="1">
<widget class="QCheckBox" name="writeTrace"> <widget class="QCheckBox" name="writeTrace">
<property name="text"> <property name="text">
<string>Write trace file</string> <string>Write trace file</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1">
<widget class="QCheckBox" name="runAtSystemStart">
<property name="text">
<string>Run at system start</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Shortcuts</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="1">
<widget class="service::KeySequenceEdit" name="clipboardEdit" native="true"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Repeat capture</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="service::KeySequenceEdit" name="repeatCaptureEdit" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Show last result</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="service::KeySequenceEdit" name="captureEdit" native="true"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Copy result to clipboard</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="service::KeySequenceEdit" name="repeatEdit" native="true"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Capture</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Capture saved areas</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="service::KeySequenceEdit" name="captureLockedEdit" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="showOnStart">
<property name="text">
<string>Show message on program start</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="portable">
<property name="text">
<string>Portable (store data in same folder)</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pageRecognize"> <widget class="QWidget" name="pageRecognize">
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Default language:</string>
</property>
<property name="buddy">
<cstring>tesseractLangCombo</cstring>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="sizePolicy"> <property name="sizePolicy">
@ -304,9 +256,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2">
<widget class="QComboBox" name="tesseractLangCombo"/>
</item>
<item row="2" column="2"> <item row="2" column="2">
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">
@ -320,6 +269,9 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="2">
<widget class="QComboBox" name="tesseractLangCombo"/>
</item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QLabel" name="tessdataPath"> <widget class="QLabel" name="tessdataPath">
<property name="text"> <property name="text">
@ -333,22 +285,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Default language:</string>
</property>
<property name="buddy">
<cstring>tesseractLangCombo</cstring>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pageCorrect"> <widget class="QWidget" name="pageCorrect">
@ -598,6 +534,9 @@
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
<property name="flat">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
@ -612,6 +551,9 @@
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
<property name="flat">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2"> <item row="4" column="0" colspan="2">
@ -661,14 +603,33 @@
</widget> </widget>
<widget class="QWidget" name="pageUpdate"> <widget class="QWidget" name="pageUpdate">
<layout class="QGridLayout" name="gridLayout_5"> <layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="0" colspan="2"> <item row="2" column="0">
<widget class="QTreeView" name="updatesView"> <spacer name="horizontalSpacer_2">
<property name="sortingEnabled"> <property name="orientation">
<bool>true</bool> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> <property name="sizeHint" stdset="0">
<size>
<width>204</width>
<height>20</height>
</size>
</property>
</spacer>
</item> </item>
<item row="0" column="0" colspan="2"> <item row="2" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>203</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="3">
<widget class="QWidget" name="widget_3" native="true"> <widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
@ -704,6 +665,20 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="3">
<widget class="QTreeView" name="updatesView">
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="applyUpdates">
<property name="text">
<string>Apply updates</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pageAbout"> <widget class="QWidget" name="pageAbout">
@ -718,16 +693,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="helpLabel">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
@ -746,18 +711,6 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</item>
<item row="1" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@ -765,11 +718,6 @@
<extends>QTableWidget</extends> <extends>QTableWidget</extends>
<header>substitutionstable.h</header> <header>substitutionstable.h</header>
</customwidget> </customwidget>
<customwidget>
<class>service::KeySequenceEdit</class>
<extends>QWidget</extends>
<header>keysequenceedit.h</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>captureEdit</tabstop> <tabstop>captureEdit</tabstop>

View File

@ -1,58 +0,0 @@
#include "settingsvalidator.h"
#include <commonmodels.h>
#include <settings.h>
QVector<SettingsValidator::Error> SettingsValidator::check(
const Settings &settings, const CommonModels &models) const
{
QVector<SettingsValidator::Error> result;
if (models.sourceLanguageModel()->rowCount() == 0)
result.append(Error::NoSourceInstalled);
if (settings.sourceLanguage.isEmpty())
result.append(Error::NoSourceSet);
if (settings.doTranslation && models.translators().isEmpty())
result.append(Error::NoTranslatorInstalled);
if (settings.doTranslation && settings.translators.isEmpty())
result.append(Error::NoTranslatorSet);
if (settings.doTranslation && settings.targetLanguage.isEmpty())
result.append(Error::NoTargetSet);
return result;
}
bool SettingsValidator::correct(Settings &settings, const CommonModels &models)
{
auto changed = false;
if (settings.doTranslation && settings.translators.isEmpty() &&
!models.translators().isEmpty()) {
settings.translators = models.translators();
changed = true;
}
return changed;
}
QString SettingsValidator::toString(Error error) const
{
switch (error) {
case Error::NoSourceInstalled:
return QObject::tr("No recognizers installed");
case Error::NoSourceSet: return QObject::tr("Recognition language not set");
case Error::NoTranslatorInstalled:
return QObject::tr("No translators installed");
case Error::NoTranslatorSet:
return QObject::tr("No translators enabled (selected)");
case Error::NoTargetSet: return QObject::tr("Translation language not set");
}
return {};
}

View File

@ -1,22 +0,0 @@
#pragma once
#include <stfwd.h>
#include <QVector>
class SettingsValidator
{
public:
enum class Error {
NoSourceInstalled,
NoSourceSet,
NoTranslatorSet,
NoTranslatorInstalled,
NoTargetSet
};
QVector<Error> check(const Settings& settings,
const CommonModels& models) const;
bool correct(Settings& settings, const CommonModels& models);
QString toString(Error error) const;
};

View File

@ -22,7 +22,8 @@ class CommonModels;
namespace update namespace update
{ {
class Updater; class Loader;
class AutoChecker;
} // namespace update } // namespace update
using TaskPtr = std::shared_ptr<Task>; using TaskPtr = std::shared_ptr<Task>;

View File

@ -55,7 +55,6 @@ Translator::Translator(Manager &manager, const Settings &settings)
} }
setObjectName("Translator"); setObjectName("Translator");
setWindowTitle(tr("Translator"));
view_ = new QWebEngineView(this); view_ = new QWebEngineView(this);
@ -104,7 +103,6 @@ void Translator::translate(const TaskPtr &task)
SOFT_ASSERT(task, return ); SOFT_ASSERT(task, return );
if (task->corrected.isEmpty()) { if (task->corrected.isEmpty()) {
LTRACE() << "Corrected text is empty. Skipping translation";
manager_.translated(task); manager_.translated(task);
return; return;
} }
@ -117,7 +115,6 @@ void Translator::updateSettings()
{ {
view_->setPage(nullptr); view_->setPage(nullptr);
pages_.clear(); pages_.clear();
queue_.clear();
url_->clear(); url_->clear();
tabs_->blockSignals(true); tabs_->blockSignals(true);
@ -128,15 +125,17 @@ void Translator::updateSettings()
} }
tabs_->blockSignals(false); tabs_->blockSignals(false);
if (settings_.translators.empty()) if (settings_.translators.empty()) {
manager_.fatalError(tr("No translators selected"));
return; return;
}
const auto loaded = const auto loaded =
loadScripts(settings_.translatorsPath, settings_.translators); loadScripts(settings_.translatorsDir, settings_.translators);
if (loaded.empty()) { if (loaded.empty()) {
manager_.fatalError( manager_.fatalError(
tr("No translators loaded from\n%1\n(%2)") tr("No translators loaded from\n%1\n(%2)")
.arg(settings_.translatorsPath, settings_.translators.join(", "))); .arg(settings_.translatorsDir, settings_.translators.join(", ")));
return; return;
} }
@ -178,7 +177,6 @@ void Translator::createPage(const QString &scriptName,
SOFT_ASSERT(log->document(), return ) SOFT_ASSERT(log->document(), return )
log->document()->setMaximumBlockCount(1000); log->document()->setMaximumBlockCount(1000);
LTRACE() << "Created page" << LARG(scriptName);
} }
void Translator::showDebugView() void Translator::showDebugView()
@ -281,7 +279,6 @@ void Translator::processQueue()
pages_[translator]->start(task); pages_[translator]->start(task);
task->translators.removeOne(translator); task->translators.removeOne(translator);
idlePages.erase(translator); idlePages.erase(translator);
LTRACE() << "Started translation at" << translator << task;
break; break;
} }
} }

View File

@ -49,8 +49,7 @@ void TrayIcon::updateSettings()
failedActions << settings_.captureLockedHotkey; failedActions << settings_.captureLockedHotkey;
if (!failedActions.isEmpty()) { if (!failedActions.isEmpty()) {
showError(tr("Failed to register global shortcuts:\n%1" showError(tr("Failed to register global shortcuts:\n%1")
"\nMost likely they are already in use by another program")
.arg(failedActions.join('\n'))); .arg(failedActions.join('\n')));
} }
} }
@ -59,16 +58,6 @@ void TrayIcon::blockActions(bool block)
{ {
isActionsBlocked_ = block; isActionsBlocked_ = block;
updateActions(); updateActions();
const auto actions =
QVector<QAction *>{captureAction_, repeatCaptureAction_, showLastAction_,
clipboardAction_, captureLockedAction_};
for (const auto i : actions) {
if (block) {
GlobalAction::removeGlobal(i);
} else {
GlobalAction::makeGlobal(i);
}
}
} }
void TrayIcon::setTaskActionsEnabled(bool isEnabled) void TrayIcon::setTaskActionsEnabled(bool isEnabled)

View File

@ -14,9 +14,10 @@ const auto f1 = "from1.txt";
const auto t1 = "test/to1.txt"; const auto t1 = "test/to1.txt";
const auto data = "sample"; const auto data = "sample";
File toFile(const QString& to) File toFile(const QString& from, const QString& to)
{ {
File result; File result;
result.downloadPath = from;
result.expandedPath = to; result.expandedPath = to;
return result; return result;
} }
@ -46,38 +47,53 @@ bool removeFile(const QString& name)
} }
} // namespace } // namespace
TEST(UpdateInstaller, Empty)
{
UserActions actions;
Installer testee(actions);
ASSERT_TRUE(testee.commit());
}
TEST(UpdateInstaller, SuccessInstall) TEST(UpdateInstaller, SuccessInstall)
{ {
ASSERT_TRUE(removeFile(t1)); ASSERT_TRUE(removeFile(t1));
ASSERT_TRUE(writeFile(f1, data));
Installer testee; UserActions actions{{Action::Install, toFile(f1, t1)}};
testee.install(toFile(t1), data); Installer testee(actions);
ASSERT_TRUE(testee.commit());
ASSERT_EQ(data, readFile(t1)); ASSERT_EQ(data, readFile(t1));
ASSERT_TRUE(testee.error().isEmpty());
} }
TEST(UpdateInstaller, SuccessRemove) TEST(UpdateInstaller, SuccessRemove)
{ {
ASSERT_TRUE(writeFile(f1, data)); ASSERT_TRUE(writeFile(f1, data));
Installer testee; UserActions actions{{Action::Remove, toFile(f1, f1)}};
testee.remove(toFile(f1)); Installer testee(actions);
ASSERT_TRUE(testee.commit());
ASSERT_FALSE(QFile::exists(f1)); ASSERT_FALSE(QFile::exists(f1));
ASSERT_TRUE(testee.error().isEmpty());
} }
#ifdef Q_OS_LINUX TEST(UpdateInstaller, FailInstallNoSource)
{
ASSERT_TRUE(removeFile(f1));
UserActions actions{{Action::Install, toFile(f1, t1)}};
Installer testee(actions);
ASSERT_FALSE(testee.commit());
}
TEST(UpdateInstaller, FailInstallNoWritable) TEST(UpdateInstaller, FailInstallNoWritable)
{ {
const auto t1 = "/foo.txt"; const auto t1 = "/foo.txt";
QFile f(t1); QFile f(t1);
ASSERT_FALSE(f.isWritable()); ASSERT_FALSE(f.isWritable());
Installer testee; UserActions actions{{Action::Install, toFile(f1, t1)}};
testee.install(toFile(t1), data); Installer testee(actions);
ASSERT_FALSE(testee.error().isEmpty()); ASSERT_FALSE(testee.commit());
} }
#endif
TEST(UpdateInstaller, FailRemove) TEST(UpdateInstaller, FailRemove)
{ {
@ -87,9 +103,9 @@ TEST(UpdateInstaller, FailRemove)
return; return;
ASSERT_FALSE(QFile::copy(f1, f1 + "1")); // non writable ASSERT_FALSE(QFile::copy(f1, f1 + "1")); // non writable
Installer testee; UserActions actions{{Action::Remove, toFile(f1, f1)}};
testee.remove(toFile(f1)); Installer testee(actions);
ASSERT_FALSE(testee.error().isEmpty()); ASSERT_FALSE(testee.commit());
} }
TEST(UpdateModel, ParseFail) TEST(UpdateModel, ParseFail)
@ -97,8 +113,7 @@ TEST(UpdateModel, ParseFail)
const auto updates = R"({ const auto updates = R"({
})"; })";
Updater updater({}); Model testee;
Model testee(updater);
const auto error = testee.parse(updates); const auto error = testee.parse(updates);
ASSERT_FALSE(error.isEmpty()); ASSERT_FALSE(error.isEmpty());
@ -119,8 +134,7 @@ TEST(UpdateModel, Parse)
} }
})"; })";
Updater updater({}); Model testee;
Model testee(updater);
const auto error = testee.parse(updates); const auto error = testee.parse(updates);
ASSERT_TRUE(error.isEmpty()); ASSERT_TRUE(error.isEmpty());
@ -132,3 +146,40 @@ TEST(UpdateModel, Parse)
comp1.sibling(comp1.row(), int(Model::Column::Name)).data().toString(); comp1.sibling(comp1.row(), int(Model::Column::Name)).data().toString();
ASSERT_EQ("comp1", comp1Name); ASSERT_EQ("comp1", comp1Name);
} }
TEST(UpdateLoader, InstallFile)
{
const auto uf = "updates.json";
const auto url = QUrl::fromLocalFile(QFileInfo(f1).absoluteFilePath());
const auto updates = QString(R"({
"version":1,
"comp1":{"files":[{"url":"%1", "path":"./%2", "md5":"1"}]}
})")
.arg(url.toString(), t1);
ASSERT_TRUE(writeFile(uf, updates.toUtf8()));
ASSERT_TRUE(writeFile(f1, updates.toUtf8()));
ASSERT_TRUE(removeFile(t1));
Loader testee({QUrl::fromLocalFile(QFileInfo(uf).absoluteFilePath())});
testee.checkForUpdates();
QCoreApplication::processEvents();
auto model = testee.model();
ASSERT_NE(nullptr, model);
ASSERT_EQ(1, model->rowCount({}));
const auto comp1 = model->index(0, int(Model::Column::Action), {});
model->setData(comp1, int(Action::Install), Qt::EditRole);
QSignalSpy okSpy(&testee, &Loader::updated);
QSignalSpy errorSpy(&testee, &Loader::error);
testee.applyUserActions();
QCoreApplication::processEvents();
ASSERT_EQ(1, okSpy.count());
ASSERT_EQ(0, errorSpy.count());
ASSERT_TRUE(QFile::exists(f1));
ASSERT_EQ(updates, readFile(t1));
}

View File

@ -20,25 +20,14 @@ function checkFinished() {
function translate(text, from, to) { function translate(text, from, to) {
console.log('start translate', text, from, to) console.log('start translate', text, from, to)
if (text.trim().length == 0) {
proxy.setTranslated('');
return;
}
active = true; active = true;
let langs = from + '/' + to; let langs = from + '/' + to;
if (window.location.href.indexOf('//fanyi.baidu.com/') !== -1 if (window.location.href.indexOf('//fanyi.baidu.com/') !== -1
&& window.location.href.indexOf(langs) !== -1) { && window.location.href.indexOf(langs) !== -1) {
var input = document.querySelector('textarea#baidu_translate_input'); document.querySelector('textarea#baidu_translate_input').value = text;
if (input.value == text) { document.querySelector('textarea#baidu_translate_input').dispatchEvent(
console.log('using cached result'); new Event("input", { bubbles: true, cancelable: true }));
lastText = '';
return;
}
input.value = text;
input.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
return; return;
} }

View File

@ -20,24 +20,13 @@ function checkFinished() {
function translate(text, from, to) { function translate(text, from, to) {
console.log('start translate', text, from, to) console.log('start translate', text, from, to)
if (text.trim().length == 0) {
proxy.setTranslated('');
return;
}
active = true; active = true;
if (window.location.href.indexOf('bing.com/translator') !== -1 if (window.location.href.indexOf('bing.com/translator') !== -1
&& window.location.href.indexOf('&to=' + to + '&') !== -1) { && window.location.href.indexOf('&to=' + to + '&') !== -1) {
var input = document.querySelector('textarea#tta_input_ta'); document.querySelector('textarea#tta_input_ta').value = text;
if (input.value == text) { document.querySelector('textarea#tta_input_ta').dispatchEvent(
console.log('using cached result'); new Event("input", { bubbles: true, cancelable: true }));
lastText = '';
return;
}
input.value = text;
input.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
return; return;
} }

View File

@ -4,16 +4,8 @@ var active = window.location.href !== "about:blank";
function checkFinished() { function checkFinished() {
if (!active) return; if (!active) return;
let area = document.querySelector('div#target-dummydiv'); let area = document.querySelector('textarea[dl-test=translator-target-input]');
let text = area ? area.innerHTML.trim() : ''; let text = area ? area.value : '';
if (area == null) {
area = document.querySelector('d-textarea.lmt__target_textarea p');
text = area ? area.innerText.trim() : '';
}
if (area == null) {
area = document.querySelector('d-textarea[data-testid=translator-target-input] p');
text = area ? area.innerText.trim() : '';
}
if (text === lastText || text === '') if (text === lastText || text === '')
return; return;
@ -26,18 +18,10 @@ function checkFinished() {
function translate(text, from, to) { function translate(text, from, to) {
console.log('start translate', text, from, to) console.log('start translate', text, from, to)
if (text.trim().length == 0) {
proxy.setTranslated('');
return;
}
from = from == 'zh-CN' ? 'zh' : from; from = from == 'zh-CN' ? 'zh' : from;
to = to == 'zh-CN' ? 'zh' : to; to = to == 'zh-CN' ? 'zh' : to;
let supported = ['ru', 'en', 'de', 'fr', 'es', 'pt', 'it', 'nl', 'pl', 'ja', 'zh', let supported = ['ru', 'en', 'de', 'fr', 'es', 'pt', 'it', 'nl', 'pl', 'ja', 'zh']
'uk', 'bg', 'hu', 'el', 'da', 'id', 'lt', 'pt', 'ro', 'sk', 'sk', 'tr', 'fi', 'cs',
'sv', 'et']
if (supported.indexOf(from) == -1) { if (supported.indexOf(from) == -1) {
proxy.setFailed('Source language not supported'); proxy.setFailed('Source language not supported');
return; return;
@ -49,32 +33,16 @@ function translate(text, from, to) {
active = true; active = true;
var singleLineText = text.replace(/(?:\r\n|\r|\n)/g, ' ');
let langs = from + '/' + to + '/'; let langs = from + '/' + to + '/';
if (window.location.href.indexOf('www.deepl.com/translator') !== -1 if (window.location.href.indexOf('www.deepl.com/translator') !== -1
&& window.location.href.indexOf(langs) !== -1) { && window.location.href.indexOf(langs) !== -1) {
document.querySelector('textarea[dl-test=translator-source-input]').value = text;
var input = document.querySelector('d-textarea[dl-test=translator-source-input] p'); document.querySelector('textarea[dl-test=translator-source-input]').dispatchEvent(
if (input == null) new Event("input", { bubbles: true, cancelable: true }));
input = document.querySelector('d-textarea.lmt__source_textarea p');
if (input == null)
input = document.querySelector('d-textarea[data-testid=translator-source-input] p');
if (input.innerText == singleLineText) {
console.log('using cached result');
lastText = '';
return;
}
input.innerText = singleLineText;
if (areaCopy = document.querySelector('div#source-dummydiv'))
areaCopy.innerHTML = singleLineText;
setTimeout(function () {
input.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
}, 300);
return; return;
} }
let url = 'https://www.deepl.com/translator#' + langs + encodeURIComponent(singleLineText); let url = 'https://www.deepl.com/translator#' + langs + encodeURIComponent(text);
console.log("setting url", url); console.log("setting url", url);
window.location = url; window.location = url;
} }

View File

@ -4,7 +4,7 @@ var active = window.location.href !== "about:blank";
function checkFinished() { function checkFinished() {
if (!active) return; if (!active) return;
let spans = [].slice.call(document.querySelectorAll('span.HwtZe > span > span')); let spans = [].slice.call(document.querySelectorAll('span.translation > span, #result_box > span'));
let text = spans.reduce(function (res, i) { let text = spans.reduce(function (res, i) {
return res + ' ' + i.innerText; return res + ' ' + i.innerText;
}, ''); }, '');
@ -21,26 +21,14 @@ function checkFinished() {
function translate(text, from, to) { function translate(text, from, to) {
console.log('start translate', text, from, to) console.log('start translate', text, from, to)
if (text.trim().length == 0) {
proxy.setTranslated('');
return;
}
active = true; active = true;
if (window.location.href.indexOf('//translate.google') !== -1 if (window.location.href.indexOf('//translate.google') !== -1
&& window.location.href.indexOf('&tl=' + to + '&') !== -1) { && window.location.href.indexOf('&tl=' + to + '&') !== -1) {
var input = document.querySelector('textarea.er8xn'); document.querySelector('textarea#source').value = text;
if (input.value == text) {
console.log('using cached result');
lastText = '';
return;
}
input.value = text;
input.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
return; return;
} }
// let url = 'https://translate.google.com/#auto/' + to + '/' + text;
let url = 'https://translate.google.com/#view=home&op=translate&sl=auto&tl=' + to + '&text=' + encodeURIComponent(text); let url = 'https://translate.google.com/#view=home&op=translate&sl=auto&tl=' + to + '&text=' + encodeURIComponent(text);
console.log("setting url", url); console.log("setting url", url);
window.location = url; window.location = url;

View File

@ -1,15 +1,8 @@
function httpGetAsync(url, callback) { function httpGetAsync(url, callback) {
let xmlHttp = new XMLHttpRequest(); let xmlHttp = new XMLHttpRequest();
xmlHttp.timeout = 30000; // msecs
xmlHttp.onreadystatechange = function () { xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState != 4) if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
return
if (xmlHttp.status == 200)
callback(xmlHttp.responseText); callback(xmlHttp.responseText);
else
proxy.setFailed(xmlHttp.statusText)
xmlHttp.onreadystatechange = null;
xmlHttp = null;
} }
xmlHttp.open("GET", url, true); xmlHttp.open("GET", url, true);
xmlHttp.send(null); xmlHttp.send(null);

View File

@ -31,12 +31,6 @@ function checkFinished() {
function translate(text, from, to) { function translate(text, from, to) {
console.log('start translate', text, from, to) console.log('start translate', text, from, to)
if (text.trim().length == 0) {
proxy.setTranslated('');
return;
}
let supported = ['ko', 'ru', 'en', 'fr', 'pt', 'th', 'ja', let supported = ['ko', 'ru', 'en', 'fr', 'pt', 'th', 'ja',
'zh-CN', 'zh-TW', 'de', 'it', 'id', 'es', 'vi', 'hi']; 'zh-CN', 'zh-TW', 'de', 'it', 'id', 'es', 'vi', 'hi'];
@ -54,14 +48,9 @@ function translate(text, from, to) {
let langs = '?sk=auto&tk=' + to + '&'; let langs = '?sk=auto&tk=' + to + '&';
if (window.location.href.indexOf('//papago.naver.com/') !== -1 if (window.location.href.indexOf('//papago.naver.com/') !== -1
&& window.location.href.indexOf(langs) !== -1) { && window.location.href.indexOf(langs) !== -1) {
var input = document.querySelector('textarea#txtSource'); document.querySelector('textarea#txtSource').value = text
if (input.value == text) { document.querySelector('textarea#txtSource').dispatchEvent(
console.log('using cached result'); new Event("input", { bubbles: true, cancelable: true }));
lastText = '';
return;
}
input.value = text;
input.dispatchEvent(new Event("input", { bubbles: true, cancelable: true }));
return; return;
} }

View File

@ -4,9 +4,9 @@ var active = window.location.href !== "about:blank";
function checkFinished() { function checkFinished() {
if (!active) return; if (!active) return;
let spans = [].slice.call(document.querySelectorAll('span.translation-word')); let spans = [].slice.call(document.querySelectorAll('span.translation-chunk'));
let text = spans.reduce(function (res, i) { let text = spans.reduce(function (res, i) {
return res + i.innerText; return res + ' ' + i.innerText;
}, '').trim(); }, '').trim();
if (text === lastText || text === '') if (text === lastText || text === '')
@ -20,21 +20,10 @@ function checkFinished() {
function translate(text, from, to) { function translate(text, from, to) {
console.log('start translate', text, from, to) console.log('start translate', text, from, to)
if (text.trim().length == 0) {
proxy.setTranslated('');
return;
}
active = true; active = true;
let langs = 'lang=' + from + '-' + to; let langs = 'lang=' + from + '-' + to;
let url = 'https://translate.yandex.ru/?' + langs + '&text=' + encodeURIComponent(text); let url = 'https://translate.yandex.ru/?' + langs + '&text=' + encodeURIComponent(text);
if (window.location.href == url) {
console.log('using cached result');
lastText = '';
return;
}
console.log("setting url", url); console.log("setting url", url);
window.location = url; window.location = url;
} }

View File

@ -2,585 +2,573 @@
"version":1 "version":1
,"app":{ ,"app":{
"win32":{"version":"3.3.0", "host":"win32", "files":[{"path":"$appdir$/screen-translator.exe", "md5":"414c74c4594e0b90aff3cd86a73f96dd"}]} "win32":{"version":"3.0.0", "host":"win32", "files":[{"path":"$appdir$/screen-translator.exe", "md5":""}]}
,"win64":{"version":"3.3.0", "host":"win64", "files":[{"path":"$appdir$/screen-translator.exe", "md5":"3f2c3c27364f25c239ea63243a8910a3"}]} ,"win64":{"version":"3.0.0", "host":"win64", "files":[{"path":"$appdir$/screen-translator.exe", "md5":""}]}
,"linux":{"version":"3.3.0", "host":"linux", "files":[{"path":"$appdir$/screen-translator", "md5":"a091be0443fd128a02b01b313e0270bc"}]} ,"linux":{"version":"3.0.0", "host":"linux", "files":[{"path":"$appdir$/screen-translator", "md5":""}]}
} }
,"recognizers": { ,"recognizers": {
"Afrikaans":{"files":[ "Afrikaans":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/afr.traineddata","https://translator.gres.biz/resources/tessdata_best/afr.traineddata.zip"], "path":"$tessdata$/afr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12800552} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/afr.traineddata","https://translator.gres.biz/resources/tessdata_best/afr.traineddata.zip"], "path":"$tessdata$/afr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12800552}
]} ]}
, "Amharic":{"files":[ , "Amharic":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/amh.traineddata","https://translator.gres.biz/resources/tessdata_best/amh.traineddata.zip"], "path":"$tessdata$/amh.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8389639} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/amh.traineddata","https://translator.gres.biz/resources/tessdata_best/amh.traineddata.zip"], "path":"$tessdata$/amh.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8389639}
]} ]}
, "Arabic":{"files":[ , "Arabic":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ara.traineddata","https://translator.gres.biz/resources/tessdata_best/ara.traineddata.zip"], "path":"$tessdata$/ara.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12603724} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ara.traineddata","https://translator.gres.biz/resources/tessdata_best/ara.traineddata.zip"], "path":"$tessdata$/ara.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12603724}
]} ]}
, "Assamese":{"files":[ , "Assamese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/asm.traineddata","https://translator.gres.biz/resources/tessdata_best/asm.traineddata.zip"], "path":"$tessdata$/asm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11315350} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/asm.traineddata","https://translator.gres.biz/resources/tessdata_best/asm.traineddata.zip"], "path":"$tessdata$/asm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11315350}
]} ]}
, "Azerbaijani":{"files":[ , "Azerbaijani":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/aze.traineddata","https://translator.gres.biz/resources/tessdata_best/aze.traineddata.zip"], "path":"$tessdata$/aze.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6281404} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/aze.traineddata","https://translator.gres.biz/resources/tessdata_best/aze.traineddata.zip"], "path":"$tessdata$/aze.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6281404}
]} ]}
, "aze_cyrl":{"files":[ , "aze_cyrl":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/aze_cyrl.traineddata","https://translator.gres.biz/resources/tessdata_best/aze_cyrl.traineddata.zip"], "path":"$tessdata$/aze_cyrl.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4700277} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/aze_cyrl.traineddata","https://translator.gres.biz/resources/tessdata_best/aze_cyrl.traineddata.zip"], "path":"$tessdata$/aze_cyrl.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4700277}
]} ]}
, "Belarusian":{"files":[ , "Belarusian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/bel.traineddata","https://translator.gres.biz/resources/tessdata_best/bel.traineddata.zip"], "path":"$tessdata$/bel.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10870278} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/bel.traineddata","https://translator.gres.biz/resources/tessdata_best/bel.traineddata.zip"], "path":"$tessdata$/bel.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10870278}
]} ]}
, "Bengali":{"files":[ , "Bengali":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ben.traineddata","https://translator.gres.biz/resources/tessdata_best/ben.traineddata.zip"], "path":"$tessdata$/ben.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11045427} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ben.traineddata","https://translator.gres.biz/resources/tessdata_best/ben.traineddata.zip"], "path":"$tessdata$/ben.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11045427}
]} ]}
, "Tibetan":{"files":[ , "Tibetan":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/bod.traineddata","https://translator.gres.biz/resources/tessdata_best/bod.traineddata.zip"], "path":"$tessdata$/bod.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8623846} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/bod.traineddata","https://translator.gres.biz/resources/tessdata_best/bod.traineddata.zip"], "path":"$tessdata$/bod.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8623846}
]} ]}
, "Bosnian":{"files":[ , "Bosnian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/bos.traineddata","https://translator.gres.biz/resources/tessdata_best/bos.traineddata.zip"], "path":"$tessdata$/bos.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5264248} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/bos.traineddata","https://translator.gres.biz/resources/tessdata_best/bos.traineddata.zip"], "path":"$tessdata$/bos.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5264248}
]} ]}
, "Breton":{"files":[ , "Breton":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/bre.traineddata","https://translator.gres.biz/resources/tessdata_best/bre.traineddata.zip"], "path":"$tessdata$/bre.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15640760} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/bre.traineddata","https://translator.gres.biz/resources/tessdata_best/bre.traineddata.zip"], "path":"$tessdata$/bre.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15640760}
]} ]}
, "Bulgarian":{"files":[ , "Bulgarian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/bul.traineddata","https://translator.gres.biz/resources/tessdata_best/bul.traineddata.zip"], "path":"$tessdata$/bul.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8844613} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/bul.traineddata","https://translator.gres.biz/resources/tessdata_best/bul.traineddata.zip"], "path":"$tessdata$/bul.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8844613}
]} ]}
, "Catalan":{"files":[ , "Catalan":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/cat.traineddata","https://translator.gres.biz/resources/tessdata_best/cat.traineddata.zip"], "path":"$tessdata$/cat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3802329} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/cat.traineddata","https://translator.gres.biz/resources/tessdata_best/cat.traineddata.zip"], "path":"$tessdata$/cat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3802329}
]} ]}
, "Cebuano":{"files":[ , "Cebuano":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ceb.traineddata","https://translator.gres.biz/resources/tessdata_best/ceb.traineddata.zip"], "path":"$tessdata$/ceb.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3452674} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ceb.traineddata","https://translator.gres.biz/resources/tessdata_best/ceb.traineddata.zip"], "path":"$tessdata$/ceb.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3452674}
]} ]}
, "Czech":{"files":[ , "Czech":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ces.traineddata","https://translator.gres.biz/resources/tessdata_best/ces.traineddata.zip"], "path":"$tessdata$/ces.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10918912} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ces.traineddata","https://translator.gres.biz/resources/tessdata_best/ces.traineddata.zip"], "path":"$tessdata$/ces.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10918912}
]} ]}
, "Chinese (Simplified)":{"files":[ , "Chinese (Simplified)":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/chi_sim.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_sim.traineddata.zip"], "path":"$tessdata$/chi_sim.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13077423} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_sim.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_sim.traineddata.zip"], "path":"$tessdata$/chi_sim.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13077423}
]} ]}
, "Chinese (Simplified) vertical":{"files":[ , "chi_sim_vert":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/chi_sim_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_sim_vert.traineddata.zip"], "path":"$tessdata$/chi_sim_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13077507} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_sim_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_sim_vert.traineddata.zip"], "path":"$tessdata$/chi_sim_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13077507}
]} ]}
, "Chinese (Traditional)":{"files":[ , "Chinese (Traditional)":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/chi_tra.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_tra.traineddata.zip"], "path":"$tessdata$/chi_tra.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12985735} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_tra.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_tra.traineddata.zip"], "path":"$tessdata$/chi_tra.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12985735}
]} ]}
, "Chinese (Traditional) vertical":{"files":[ , "chi_tra_vert":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/chi_tra_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_tra_vert.traineddata.zip"], "path":"$tessdata$/chi_tra_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12985521} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/chi_tra_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/chi_tra_vert.traineddata.zip"], "path":"$tessdata$/chi_tra_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12985521}
]} ]}
, "Cherokee":{"files":[ , "Cherokee":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/chr.traineddata","https://translator.gres.biz/resources/tessdata_best/chr.traineddata.zip"], "path":"$tessdata$/chr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":2258703} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/chr.traineddata","https://translator.gres.biz/resources/tessdata_best/chr.traineddata.zip"], "path":"$tessdata$/chr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":2258703}
]} ]}
, "Corsican":{"files":[ , "Corsican":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/cos.traineddata","https://translator.gres.biz/resources/tessdata_best/cos.traineddata.zip"], "path":"$tessdata$/cos.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8830216} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/cos.traineddata","https://translator.gres.biz/resources/tessdata_best/cos.traineddata.zip"], "path":"$tessdata$/cos.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8830216}
]} ]}
, "Welsh":{"files":[ , "Welsh":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/cym.traineddata","https://translator.gres.biz/resources/tessdata_best/cym.traineddata.zip"], "path":"$tessdata$/cym.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8750784} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/cym.traineddata","https://translator.gres.biz/resources/tessdata_best/cym.traineddata.zip"], "path":"$tessdata$/cym.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8750784}
]} ]}
, "Danish":{"files":[ , "Danish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/dan.traineddata","https://translator.gres.biz/resources/tessdata_best/dan.traineddata.zip"], "path":"$tessdata$/dan.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9758142} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/dan.traineddata","https://translator.gres.biz/resources/tessdata_best/dan.traineddata.zip"], "path":"$tessdata$/dan.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9758142}
]} ]}
, "German":{"files":[ , "German":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/deu.traineddata","https://translator.gres.biz/resources/tessdata_best/deu.traineddata.zip"], "path":"$tessdata$/deu.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8628461} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/deu.traineddata","https://translator.gres.biz/resources/tessdata_best/deu.traineddata.zip"], "path":"$tessdata$/deu.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8628461}
]} ]}
, "Divehi, Dhivehi, Maldivian":{"files":[ , "Divehi, Dhivehi, Maldivian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/div.traineddata","https://translator.gres.biz/resources/tessdata_best/div.traineddata.zip"], "path":"$tessdata$/div.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4574116} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/div.traineddata","https://translator.gres.biz/resources/tessdata_best/div.traineddata.zip"], "path":"$tessdata$/div.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4574116}
]} ]}
, "Dzongkha":{"files":[ , "Dzongkha":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/dzo.traineddata","https://translator.gres.biz/resources/tessdata_best/dzo.traineddata.zip"], "path":"$tessdata$/dzo.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3243805} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/dzo.traineddata","https://translator.gres.biz/resources/tessdata_best/dzo.traineddata.zip"], "path":"$tessdata$/dzo.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3243805}
]} ]}
, "Greek":{"files":[ , "Greek":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ell.traineddata","https://translator.gres.biz/resources/tessdata_best/ell.traineddata.zip"], "path":"$tessdata$/ell.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8945021} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ell.traineddata","https://translator.gres.biz/resources/tessdata_best/ell.traineddata.zip"], "path":"$tessdata$/ell.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8945021}
]} ]}
, "English":{"files":[ , "English":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata","https://translator.gres.biz/resources/tessdata_best/eng.traineddata.zip"], "path":"$tessdata$/eng.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15400601} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/eng.traineddata","https://translator.gres.biz/resources/tessdata_best/eng.traineddata.zip"], "path":"$tessdata$/eng.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15400601}
]} ]}
, "English, Middle (1100-1500)":{"files":[ , "English, Middle (1100-1500)":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/enm.traineddata","https://translator.gres.biz/resources/tessdata_best/enm.traineddata.zip"], "path":"$tessdata$/enm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13281564} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/enm.traineddata","https://translator.gres.biz/resources/tessdata_best/enm.traineddata.zip"], "path":"$tessdata$/enm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13281564}
]} ]}
, "Esperanto":{"files":[ , "Esperanto":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/epo.traineddata","https://translator.gres.biz/resources/tessdata_best/epo.traineddata.zip"], "path":"$tessdata$/epo.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7402169} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/epo.traineddata","https://translator.gres.biz/resources/tessdata_best/epo.traineddata.zip"], "path":"$tessdata$/epo.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7402169}
]} ]}
, "Estonian":{"files":[ , "Estonian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/est.traineddata","https://translator.gres.biz/resources/tessdata_best/est.traineddata.zip"], "path":"$tessdata$/est.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15833749} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/est.traineddata","https://translator.gres.biz/resources/tessdata_best/est.traineddata.zip"], "path":"$tessdata$/est.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15833749}
]} ]}
, "Basque":{"files":[ , "Basque":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/eus.traineddata","https://translator.gres.biz/resources/tessdata_best/eus.traineddata.zip"], "path":"$tessdata$/eus.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7933869} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/eus.traineddata","https://translator.gres.biz/resources/tessdata_best/eus.traineddata.zip"], "path":"$tessdata$/eus.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7933869}
]} ]}
, "Faroese":{"files":[ , "Faroese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/fao.traineddata","https://translator.gres.biz/resources/tessdata_best/fao.traineddata.zip"], "path":"$tessdata$/fao.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10030003} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/fao.traineddata","https://translator.gres.biz/resources/tessdata_best/fao.traineddata.zip"], "path":"$tessdata$/fao.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10030003}
]} ]}
, "Persian":{"files":[ , "Persian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/fas.traineddata","https://translator.gres.biz/resources/tessdata_best/fas.traineddata.zip"], "path":"$tessdata$/fas.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3325955} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/fas.traineddata","https://translator.gres.biz/resources/tessdata_best/fas.traineddata.zip"], "path":"$tessdata$/fas.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3325955}
]} ]}
, "Filipino":{"files":[ , "Filipino":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/fil.traineddata","https://translator.gres.biz/resources/tessdata_best/fil.traineddata.zip"], "path":"$tessdata$/fil.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8978743} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/fil.traineddata","https://translator.gres.biz/resources/tessdata_best/fil.traineddata.zip"], "path":"$tessdata$/fil.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8978743}
]} ]}
, "Finnish":{"files":[ , "Finnish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/fin.traineddata","https://translator.gres.biz/resources/tessdata_best/fin.traineddata.zip"], "path":"$tessdata$/fin.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14369979} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/fin.traineddata","https://translator.gres.biz/resources/tessdata_best/fin.traineddata.zip"], "path":"$tessdata$/fin.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14369979}
]} ]}
, "French":{"files":[ , "French":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/fra.traineddata","https://translator.gres.biz/resources/tessdata_best/fra.traineddata.zip"], "path":"$tessdata$/fra.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3972885} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/fra.traineddata","https://translator.gres.biz/resources/tessdata_best/fra.traineddata.zip"], "path":"$tessdata$/fra.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3972885}
]} ]}
, "frk":{"files":[ , "frk":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/frk.traineddata","https://translator.gres.biz/resources/tessdata_best/frk.traineddata.zip"], "path":"$tessdata$/frk.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12938047} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/frk.traineddata","https://translator.gres.biz/resources/tessdata_best/frk.traineddata.zip"], "path":"$tessdata$/frk.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12938047}
]} ]}
, "French, Middle (ca.1400-1600)":{"files":[ , "French, Middle (ca.1400-1600)":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/frm.traineddata","https://translator.gres.biz/resources/tessdata_best/frm.traineddata.zip"], "path":"$tessdata$/frm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4043005} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/frm.traineddata","https://translator.gres.biz/resources/tessdata_best/frm.traineddata.zip"], "path":"$tessdata$/frm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4043005}
]} ]}
, "Western Frisian":{"files":[ , "Western Frisian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/fry.traineddata","https://translator.gres.biz/resources/tessdata_best/fry.traineddata.zip"], "path":"$tessdata$/fry.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8442509} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/fry.traineddata","https://translator.gres.biz/resources/tessdata_best/fry.traineddata.zip"], "path":"$tessdata$/fry.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8442509}
]} ]}
, "Gaelic":{"files":[ , "Gaelic":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/gla.traineddata","https://translator.gres.biz/resources/tessdata_best/gla.traineddata.zip"], "path":"$tessdata$/gla.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9599424} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/gla.traineddata","https://translator.gres.biz/resources/tessdata_best/gla.traineddata.zip"], "path":"$tessdata$/gla.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9599424}
]} ]}
, "Irish":{"files":[ , "Irish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/gle.traineddata","https://translator.gres.biz/resources/tessdata_best/gle.traineddata.zip"], "path":"$tessdata$/gle.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3942458} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/gle.traineddata","https://translator.gres.biz/resources/tessdata_best/gle.traineddata.zip"], "path":"$tessdata$/gle.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3942458}
]} ]}
, "Galician":{"files":[ , "Galician":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/glg.traineddata","https://translator.gres.biz/resources/tessdata_best/glg.traineddata.zip"], "path":"$tessdata$/glg.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12709487} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/glg.traineddata","https://translator.gres.biz/resources/tessdata_best/glg.traineddata.zip"], "path":"$tessdata$/glg.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12709487}
]} ]}
, "Greek, Ancient (to 1453)":{"files":[ , "Greek, Ancient (to 1453)":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/grc.traineddata","https://translator.gres.biz/resources/tessdata_best/grc.traineddata.zip"], "path":"$tessdata$/grc.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5168122} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/grc.traineddata","https://translator.gres.biz/resources/tessdata_best/grc.traineddata.zip"], "path":"$tessdata$/grc.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5168122}
]} ]}
, "Gujarati":{"files":[ , "Gujarati":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/guj.traineddata","https://translator.gres.biz/resources/tessdata_best/guj.traineddata.zip"], "path":"$tessdata$/guj.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8515761} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/guj.traineddata","https://translator.gres.biz/resources/tessdata_best/guj.traineddata.zip"], "path":"$tessdata$/guj.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8515761}
]} ]}
, "Haitian":{"files":[ , "Haitian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/hat.traineddata","https://translator.gres.biz/resources/tessdata_best/hat.traineddata.zip"], "path":"$tessdata$/hat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12128251} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/hat.traineddata","https://translator.gres.biz/resources/tessdata_best/hat.traineddata.zip"], "path":"$tessdata$/hat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12128251}
]} ]}
, "Hebrew":{"files":[ , "Hebrew":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/heb.traineddata","https://translator.gres.biz/resources/tessdata_best/heb.traineddata.zip"], "path":"$tessdata$/heb.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3704077} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/heb.traineddata","https://translator.gres.biz/resources/tessdata_best/heb.traineddata.zip"], "path":"$tessdata$/heb.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3704077}
]} ]}
, "Hindi":{"files":[ , "Hindi":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/hin.traineddata","https://translator.gres.biz/resources/tessdata_best/hin.traineddata.zip"], "path":"$tessdata$/hin.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11895564} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/hin.traineddata","https://translator.gres.biz/resources/tessdata_best/hin.traineddata.zip"], "path":"$tessdata$/hin.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11895564}
]} ]}
, "Croatian":{"files":[ , "Croatian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/hrv.traineddata","https://translator.gres.biz/resources/tessdata_best/hrv.traineddata.zip"], "path":"$tessdata$/hrv.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11195424} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/hrv.traineddata","https://translator.gres.biz/resources/tessdata_best/hrv.traineddata.zip"], "path":"$tessdata$/hrv.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11195424}
]} ]}
, "Hungarian":{"files":[ , "Hungarian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/hun.traineddata","https://translator.gres.biz/resources/tessdata_best/hun.traineddata.zip"], "path":"$tessdata$/hun.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12350405} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/hun.traineddata","https://translator.gres.biz/resources/tessdata_best/hun.traineddata.zip"], "path":"$tessdata$/hun.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12350405}
]} ]}
, "Armenian":{"files":[ , "Armenian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/hye.traineddata","https://translator.gres.biz/resources/tessdata_best/hye.traineddata.zip"], "path":"$tessdata$/hye.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6372242} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/hye.traineddata","https://translator.gres.biz/resources/tessdata_best/hye.traineddata.zip"], "path":"$tessdata$/hye.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6372242}
]} ]}
, "Inuktitut":{"files":[ , "Inuktitut":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/iku.traineddata","https://translator.gres.biz/resources/tessdata_best/iku.traineddata.zip"], "path":"$tessdata$/iku.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6139484} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/iku.traineddata","https://translator.gres.biz/resources/tessdata_best/iku.traineddata.zip"], "path":"$tessdata$/iku.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6139484}
]} ]}
, "Indonesian":{"files":[ , "Indonesian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ind.traineddata","https://translator.gres.biz/resources/tessdata_best/ind.traineddata.zip"], "path":"$tessdata$/ind.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8253606} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ind.traineddata","https://translator.gres.biz/resources/tessdata_best/ind.traineddata.zip"], "path":"$tessdata$/ind.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8253606}
]} ]}
, "Icelandic":{"files":[ , "Icelandic":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/isl.traineddata","https://translator.gres.biz/resources/tessdata_best/isl.traineddata.zip"], "path":"$tessdata$/isl.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9486436} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/isl.traineddata","https://translator.gres.biz/resources/tessdata_best/isl.traineddata.zip"], "path":"$tessdata$/isl.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9486436}
]} ]}
, "Italian":{"files":[ , "Italian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ita.traineddata","https://translator.gres.biz/resources/tessdata_best/ita.traineddata.zip"], "path":"$tessdata$/ita.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8863667} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ita.traineddata","https://translator.gres.biz/resources/tessdata_best/ita.traineddata.zip"], "path":"$tessdata$/ita.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8863667}
]} ]}
, "ita_old":{"files":[ , "ita_old":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ita_old.traineddata","https://translator.gres.biz/resources/tessdata_best/ita_old.traineddata.zip"], "path":"$tessdata$/ita_old.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9852171} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ita_old.traineddata","https://translator.gres.biz/resources/tessdata_best/ita_old.traineddata.zip"], "path":"$tessdata$/ita_old.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9852171}
]} ]}
, "Javanese":{"files":[ , "Javanese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/jav.traineddata","https://translator.gres.biz/resources/tessdata_best/jav.traineddata.zip"], "path":"$tessdata$/jav.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8650382} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/jav.traineddata","https://translator.gres.biz/resources/tessdata_best/jav.traineddata.zip"], "path":"$tessdata$/jav.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8650382}
]} ]}
, "Japanese":{"files":[ , "Japanese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/jpn.traineddata","https://translator.gres.biz/resources/tessdata_best/jpn.traineddata.zip"], "path":"$tessdata$/jpn.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14330109} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/jpn.traineddata","https://translator.gres.biz/resources/tessdata_best/jpn.traineddata.zip"], "path":"$tessdata$/jpn.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14330109}
]} ]}
, "Japanese vertical":{"files":[ , "jpn_vert":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/jpn_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/jpn_vert.traineddata.zip"], "path":"$tessdata$/jpn_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14330809} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/jpn_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/jpn_vert.traineddata.zip"], "path":"$tessdata$/jpn_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14330809}
]} ]}
, "Kannada":{"files":[ , "Kannada":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kan.traineddata","https://translator.gres.biz/resources/tessdata_best/kan.traineddata.zip"], "path":"$tessdata$/kan.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10233763} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kan.traineddata","https://translator.gres.biz/resources/tessdata_best/kan.traineddata.zip"], "path":"$tessdata$/kan.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10233763}
]} ]}
, "Georgian":{"files":[ , "Georgian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kat.traineddata","https://translator.gres.biz/resources/tessdata_best/kat.traineddata.zip"], "path":"$tessdata$/kat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4487336} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kat.traineddata","https://translator.gres.biz/resources/tessdata_best/kat.traineddata.zip"], "path":"$tessdata$/kat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4487336}
]} ]}
, "kat_old":{"files":[ , "kat_old":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kat_old.traineddata","https://translator.gres.biz/resources/tessdata_best/kat_old.traineddata.zip"], "path":"$tessdata$/kat_old.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3174400} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kat_old.traineddata","https://translator.gres.biz/resources/tessdata_best/kat_old.traineddata.zip"], "path":"$tessdata$/kat_old.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3174400}
]} ]}
, "Kazakh":{"files":[ , "Kazakh":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kaz.traineddata","https://translator.gres.biz/resources/tessdata_best/kaz.traineddata.zip"], "path":"$tessdata$/kaz.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7528853} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kaz.traineddata","https://translator.gres.biz/resources/tessdata_best/kaz.traineddata.zip"], "path":"$tessdata$/kaz.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7528853}
]} ]}
, "Central Khmer":{"files":[ , "Central Khmer":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/khm.traineddata","https://translator.gres.biz/resources/tessdata_best/khm.traineddata.zip"], "path":"$tessdata$/khm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8104332} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/khm.traineddata","https://translator.gres.biz/resources/tessdata_best/khm.traineddata.zip"], "path":"$tessdata$/khm.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8104332}
]} ]}
, "Kyrgyz":{"files":[ , "Kyrgyz":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kir.traineddata","https://translator.gres.biz/resources/tessdata_best/kir.traineddata.zip"], "path":"$tessdata$/kir.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11948344} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kir.traineddata","https://translator.gres.biz/resources/tessdata_best/kir.traineddata.zip"], "path":"$tessdata$/kir.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11948344}
]} ]}
, "kmr":{"files":[ , "kmr":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kmr.traineddata","https://translator.gres.biz/resources/tessdata_best/kmr.traineddata.zip"], "path":"$tessdata$/kmr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10196464} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kmr.traineddata","https://translator.gres.biz/resources/tessdata_best/kmr.traineddata.zip"], "path":"$tessdata$/kmr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10196464}
]} ]}
, "Korean":{"files":[ , "Korean":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kor.traineddata","https://translator.gres.biz/resources/tessdata_best/kor.traineddata.zip"], "path":"$tessdata$/kor.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12528128} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kor.traineddata","https://translator.gres.biz/resources/tessdata_best/kor.traineddata.zip"], "path":"$tessdata$/kor.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12528128}
]} ]}
, "Korean vertical":{"files":[ , "kor_vert":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/kor_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/kor_vert.traineddata.zip"], "path":"$tessdata$/kor_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3964469} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/kor_vert.traineddata","https://translator.gres.biz/resources/tessdata_best/kor_vert.traineddata.zip"], "path":"$tessdata$/kor_vert.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3964469}
]} ]}
, "Lao":{"files":[ , "Lao":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/lao.traineddata","https://translator.gres.biz/resources/tessdata_best/lao.traineddata.zip"], "path":"$tessdata$/lao.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13532551} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/lao.traineddata","https://translator.gres.biz/resources/tessdata_best/lao.traineddata.zip"], "path":"$tessdata$/lao.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13532551}
]} ]}
, "Latin":{"files":[ , "Latin":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/lat.traineddata","https://translator.gres.biz/resources/tessdata_best/lat.traineddata.zip"], "path":"$tessdata$/lat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9705145} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/lat.traineddata","https://translator.gres.biz/resources/tessdata_best/lat.traineddata.zip"], "path":"$tessdata$/lat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9705145}
]} ]}
, "Latvian":{"files":[ , "Latvian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/lav.traineddata","https://translator.gres.biz/resources/tessdata_best/lav.traineddata.zip"], "path":"$tessdata$/lav.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5623473} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/lav.traineddata","https://translator.gres.biz/resources/tessdata_best/lav.traineddata.zip"], "path":"$tessdata$/lav.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5623473}
]} ]}
, "Lithuanian":{"files":[ , "Lithuanian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/lit.traineddata","https://translator.gres.biz/resources/tessdata_best/lit.traineddata.zip"], "path":"$tessdata$/lit.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10252680} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/lit.traineddata","https://translator.gres.biz/resources/tessdata_best/lit.traineddata.zip"], "path":"$tessdata$/lit.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10252680}
]} ]}
, "Luxembourgish":{"files":[ , "Luxembourgish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ltz.traineddata","https://translator.gres.biz/resources/tessdata_best/ltz.traineddata.zip"], "path":"$tessdata$/ltz.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12721945} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ltz.traineddata","https://translator.gres.biz/resources/tessdata_best/ltz.traineddata.zip"], "path":"$tessdata$/ltz.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12721945}
]} ]}
, "Malayalam":{"files":[ , "Malayalam":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/mal.traineddata","https://translator.gres.biz/resources/tessdata_best/mal.traineddata.zip"], "path":"$tessdata$/mal.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12524967} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/mal.traineddata","https://translator.gres.biz/resources/tessdata_best/mal.traineddata.zip"], "path":"$tessdata$/mal.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12524967}
]} ]}
, "Marathi":{"files":[ , "Marathi":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/mar.traineddata","https://translator.gres.biz/resources/tessdata_best/mar.traineddata.zip"], "path":"$tessdata$/mar.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13437670} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/mar.traineddata","https://translator.gres.biz/resources/tessdata_best/mar.traineddata.zip"], "path":"$tessdata$/mar.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13437670}
]} ]}
, "Macedonian":{"files":[ , "Macedonian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/mkd.traineddata","https://translator.gres.biz/resources/tessdata_best/mkd.traineddata.zip"], "path":"$tessdata$/mkd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3453054} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/mkd.traineddata","https://translator.gres.biz/resources/tessdata_best/mkd.traineddata.zip"], "path":"$tessdata$/mkd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3453054}
]} ]}
, "Maltese":{"files":[ , "Maltese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/mlt.traineddata","https://translator.gres.biz/resources/tessdata_best/mlt.traineddata.zip"], "path":"$tessdata$/mlt.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5060029} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/mlt.traineddata","https://translator.gres.biz/resources/tessdata_best/mlt.traineddata.zip"], "path":"$tessdata$/mlt.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5060029}
]} ]}
, "Mongolian":{"files":[ , "Mongolian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/mon.traineddata","https://translator.gres.biz/resources/tessdata_best/mon.traineddata.zip"], "path":"$tessdata$/mon.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8646663} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/mon.traineddata","https://translator.gres.biz/resources/tessdata_best/mon.traineddata.zip"], "path":"$tessdata$/mon.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8646663}
]} ]}
, "Maori":{"files":[ , "Maori":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/mri.traineddata","https://translator.gres.biz/resources/tessdata_best/mri.traineddata.zip"], "path":"$tessdata$/mri.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3610177} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/mri.traineddata","https://translator.gres.biz/resources/tessdata_best/mri.traineddata.zip"], "path":"$tessdata$/mri.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3610177}
]} ]}
, "Malay":{"files":[ , "Malay":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/msa.traineddata","https://translator.gres.biz/resources/tessdata_best/msa.traineddata.zip"], "path":"$tessdata$/msa.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8230552} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/msa.traineddata","https://translator.gres.biz/resources/tessdata_best/msa.traineddata.zip"], "path":"$tessdata$/msa.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8230552}
]} ]}
, "Burmese":{"files":[ , "Burmese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/mya.traineddata","https://translator.gres.biz/resources/tessdata_best/mya.traineddata.zip"], "path":"$tessdata$/mya.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14971060} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/mya.traineddata","https://translator.gres.biz/resources/tessdata_best/mya.traineddata.zip"], "path":"$tessdata$/mya.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14971060}
]} ]}
, "Nepali":{"files":[ , "Nepali":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/nep.traineddata","https://translator.gres.biz/resources/tessdata_best/nep.traineddata.zip"], "path":"$tessdata$/nep.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12387399} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/nep.traineddata","https://translator.gres.biz/resources/tessdata_best/nep.traineddata.zip"], "path":"$tessdata$/nep.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12387399}
]} ]}
, "Dutch":{"files":[ , "Dutch":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/nld.traineddata","https://translator.gres.biz/resources/tessdata_best/nld.traineddata.zip"], "path":"$tessdata$/nld.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8903736} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/nld.traineddata","https://translator.gres.biz/resources/tessdata_best/nld.traineddata.zip"], "path":"$tessdata$/nld.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8903736}
]} ]}
, "Norwegian":{"files":[ , "Norwegian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/nor.traineddata","https://translator.gres.biz/resources/tessdata_best/nor.traineddata.zip"], "path":"$tessdata$/nor.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14312333} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/nor.traineddata","https://translator.gres.biz/resources/tessdata_best/nor.traineddata.zip"], "path":"$tessdata$/nor.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14312333}
]} ]}
, "Occitan":{"files":[ , "Occitan":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/oci.traineddata","https://translator.gres.biz/resources/tessdata_best/oci.traineddata.zip"], "path":"$tessdata$/oci.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12917692} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/oci.traineddata","https://translator.gres.biz/resources/tessdata_best/oci.traineddata.zip"], "path":"$tessdata$/oci.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12917692}
]} ]}
, "Oriya":{"files":[ , "Oriya":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ori.traineddata","https://translator.gres.biz/resources/tessdata_best/ori.traineddata.zip"], "path":"$tessdata$/ori.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8110602} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ori.traineddata","https://translator.gres.biz/resources/tessdata_best/ori.traineddata.zip"], "path":"$tessdata$/ori.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8110602}
]} ]}
, "osd":{"files":[ , "osd":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/osd.traineddata","https://translator.gres.biz/resources/tessdata_best/osd.traineddata.zip"], "path":"$tessdata$/osd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10562727} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/osd.traineddata","https://translator.gres.biz/resources/tessdata_best/osd.traineddata.zip"], "path":"$tessdata$/osd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10562727}
]} ]}
, "Punjabi":{"files":[ , "Punjabi":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/pan.traineddata","https://translator.gres.biz/resources/tessdata_best/pan.traineddata.zip"], "path":"$tessdata$/pan.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11893154} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/pan.traineddata","https://translator.gres.biz/resources/tessdata_best/pan.traineddata.zip"], "path":"$tessdata$/pan.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11893154}
]} ]}
, "Polish":{"files":[ , "Polish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/pol.traineddata","https://translator.gres.biz/resources/tessdata_best/pol.traineddata.zip"], "path":"$tessdata$/pol.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11978867} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/pol.traineddata","https://translator.gres.biz/resources/tessdata_best/pol.traineddata.zip"], "path":"$tessdata$/pol.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11978867}
]} ]}
, "Portuguese":{"files":[ , "Portuguese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/por.traineddata","https://translator.gres.biz/resources/tessdata_best/por.traineddata.zip"], "path":"$tessdata$/por.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8159939} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/por.traineddata","https://translator.gres.biz/resources/tessdata_best/por.traineddata.zip"], "path":"$tessdata$/por.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8159939}
]} ]}
, "Pashto":{"files":[ , "Pashto":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/pus.traineddata","https://translator.gres.biz/resources/tessdata_best/pus.traineddata.zip"], "path":"$tessdata$/pus.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11987930} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/pus.traineddata","https://translator.gres.biz/resources/tessdata_best/pus.traineddata.zip"], "path":"$tessdata$/pus.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11987930}
]} ]}
, "Quechua":{"files":[ , "Quechua":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/que.traineddata","https://translator.gres.biz/resources/tessdata_best/que.traineddata.zip"], "path":"$tessdata$/que.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10774587} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/que.traineddata","https://translator.gres.biz/resources/tessdata_best/que.traineddata.zip"], "path":"$tessdata$/que.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10774587}
]} ]}
, "Romanian":{"files":[ , "Romanian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ron.traineddata","https://translator.gres.biz/resources/tessdata_best/ron.traineddata.zip"], "path":"$tessdata$/ron.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9595755} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ron.traineddata","https://translator.gres.biz/resources/tessdata_best/ron.traineddata.zip"], "path":"$tessdata$/ron.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9595755}
]} ]}
, "Russian":{"files":[ , "Russian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/rus.traineddata","https://translator.gres.biz/resources/tessdata_best/rus.traineddata.zip"], "path":"$tessdata$/rus.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15301764} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/rus.traineddata","https://translator.gres.biz/resources/tessdata_best/rus.traineddata.zip"], "path":"$tessdata$/rus.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15301764}
]} ]}
, "Sanskrit":{"files":[ , "Sanskrit":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/san.traineddata","https://translator.gres.biz/resources/tessdata_best/san.traineddata.zip"], "path":"$tessdata$/san.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15136202} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/san.traineddata","https://translator.gres.biz/resources/tessdata_best/san.traineddata.zip"], "path":"$tessdata$/san.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":15136202}
]} ]}
, "Sinhala, Sinhalese":{"files":[ , "Sinhala, Sinhalese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/sin.traineddata","https://translator.gres.biz/resources/tessdata_best/sin.traineddata.zip"], "path":"$tessdata$/sin.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8282713} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/sin.traineddata","https://translator.gres.biz/resources/tessdata_best/sin.traineddata.zip"], "path":"$tessdata$/sin.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":8282713}
]} ]}
, "Slovak":{"files":[ , "Slovak":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/slk.traineddata","https://translator.gres.biz/resources/tessdata_best/slk.traineddata.zip"], "path":"$tessdata$/slk.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11542252} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/slk.traineddata","https://translator.gres.biz/resources/tessdata_best/slk.traineddata.zip"], "path":"$tessdata$/slk.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11542252}
]} ]}
, "Slovenian":{"files":[ , "Slovenian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/slv.traineddata","https://translator.gres.biz/resources/tessdata_best/slv.traineddata.zip"], "path":"$tessdata$/slv.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5879151} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/slv.traineddata","https://translator.gres.biz/resources/tessdata_best/slv.traineddata.zip"], "path":"$tessdata$/slv.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":5879151}
]} ]}
, "Sindhi":{"files":[ , "Sindhi":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/snd.traineddata","https://translator.gres.biz/resources/tessdata_best/snd.traineddata.zip"], "path":"$tessdata$/snd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11981538} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/snd.traineddata","https://translator.gres.biz/resources/tessdata_best/snd.traineddata.zip"], "path":"$tessdata$/snd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":11981538}
]} ]}
, "Spanish":{"files":[ , "Spanish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/spa.traineddata","https://translator.gres.biz/resources/tessdata_best/spa.traineddata.zip"], "path":"$tessdata$/spa.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13570187} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/spa.traineddata","https://translator.gres.biz/resources/tessdata_best/spa.traineddata.zip"], "path":"$tessdata$/spa.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13570187}
]} ]}
, "spa_old":{"files":[ , "spa_old":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/spa_old.traineddata","https://translator.gres.biz/resources/tessdata_best/spa_old.traineddata.zip"], "path":"$tessdata$/spa_old.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9476925} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/spa_old.traineddata","https://translator.gres.biz/resources/tessdata_best/spa_old.traineddata.zip"], "path":"$tessdata$/spa_old.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9476925}
]} ]}
, "Albanian":{"files":[ , "Albanian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/sqi.traineddata","https://translator.gres.biz/resources/tessdata_best/sqi.traineddata.zip"], "path":"$tessdata$/sqi.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4631498} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/sqi.traineddata","https://translator.gres.biz/resources/tessdata_best/sqi.traineddata.zip"], "path":"$tessdata$/sqi.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4631498}
]} ]}
, "Serbian":{"files":[ , "Serbian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/srp.traineddata","https://translator.gres.biz/resources/tessdata_best/srp.traineddata.zip"], "path":"$tessdata$/srp.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9345851} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/srp.traineddata","https://translator.gres.biz/resources/tessdata_best/srp.traineddata.zip"], "path":"$tessdata$/srp.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9345851}
]} ]}
, "srp_latn":{"files":[ , "srp_latn":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/srp_latn.traineddata","https://translator.gres.biz/resources/tessdata_best/srp_latn.traineddata.zip"], "path":"$tessdata$/srp_latn.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9831713} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/srp_latn.traineddata","https://translator.gres.biz/resources/tessdata_best/srp_latn.traineddata.zip"], "path":"$tessdata$/srp_latn.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9831713}
]} ]}
, "Sundanese":{"files":[ , "Sundanese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/sun.traineddata","https://translator.gres.biz/resources/tessdata_best/sun.traineddata.zip"], "path":"$tessdata$/sun.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4132820} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/sun.traineddata","https://translator.gres.biz/resources/tessdata_best/sun.traineddata.zip"], "path":"$tessdata$/sun.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4132820}
]} ]}
, "Swahili":{"files":[ , "Swahili":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/swa.traineddata","https://translator.gres.biz/resources/tessdata_best/swa.traineddata.zip"], "path":"$tessdata$/swa.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4914855} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/swa.traineddata","https://translator.gres.biz/resources/tessdata_best/swa.traineddata.zip"], "path":"$tessdata$/swa.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4914855}
]} ]}
, "Swedish":{"files":[ , "Swedish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/swe.traineddata","https://translator.gres.biz/resources/tessdata_best/swe.traineddata.zip"], "path":"$tessdata$/swe.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14325549} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/swe.traineddata","https://translator.gres.biz/resources/tessdata_best/swe.traineddata.zip"], "path":"$tessdata$/swe.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":14325549}
]} ]}
, "Syriac":{"files":[ , "Syriac":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/syr.traineddata","https://translator.gres.biz/resources/tessdata_best/syr.traineddata.zip"], "path":"$tessdata$/syr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12498294} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/syr.traineddata","https://translator.gres.biz/resources/tessdata_best/syr.traineddata.zip"], "path":"$tessdata$/syr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12498294}
]} ]}
, "Tamil":{"files":[ , "Tamil":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/tam.traineddata","https://translator.gres.biz/resources/tessdata_best/tam.traineddata.zip"], "path":"$tessdata$/tam.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6023201} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/tam.traineddata","https://translator.gres.biz/resources/tessdata_best/tam.traineddata.zip"], "path":"$tessdata$/tam.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":6023201}
]} ]}
, "Tatar":{"files":[ , "Tatar":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/tat.traineddata","https://translator.gres.biz/resources/tessdata_best/tat.traineddata.zip"], "path":"$tessdata$/tat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7585204} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/tat.traineddata","https://translator.gres.biz/resources/tessdata_best/tat.traineddata.zip"], "path":"$tessdata$/tat.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7585204}
]} ]}
, "Telugu":{"files":[ , "Telugu":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/tel.traineddata","https://translator.gres.biz/resources/tessdata_best/tel.traineddata.zip"], "path":"$tessdata$/tel.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9098795} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/tel.traineddata","https://translator.gres.biz/resources/tessdata_best/tel.traineddata.zip"], "path":"$tessdata$/tel.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":9098795}
]} ]}
, "Tajik":{"files":[ , "Tajik":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/tgk.traineddata","https://translator.gres.biz/resources/tessdata_best/tgk.traineddata.zip"], "path":"$tessdata$/tgk.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4602842} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/tgk.traineddata","https://translator.gres.biz/resources/tessdata_best/tgk.traineddata.zip"], "path":"$tessdata$/tgk.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4602842}
]} ]}
, "Thai":{"files":[ , "Thai":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/tha.traineddata","https://translator.gres.biz/resources/tessdata_best/tha.traineddata.zip"], "path":"$tessdata$/tha.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7614571} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/tha.traineddata","https://translator.gres.biz/resources/tessdata_best/tha.traineddata.zip"], "path":"$tessdata$/tha.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7614571}
]} ]}
, "Tigrinya":{"files":[ , "Tigrinya":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/tir.traineddata","https://translator.gres.biz/resources/tessdata_best/tir.traineddata.zip"], "path":"$tessdata$/tir.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":2410256} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/tir.traineddata","https://translator.gres.biz/resources/tessdata_best/tir.traineddata.zip"], "path":"$tessdata$/tir.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":2410256}
]} ]}
, "Tonga (Tonga Islands)":{"files":[ , "Tonga (Tonga Islands)":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ton.traineddata","https://translator.gres.biz/resources/tessdata_best/ton.traineddata.zip"], "path":"$tessdata$/ton.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3729371} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ton.traineddata","https://translator.gres.biz/resources/tessdata_best/ton.traineddata.zip"], "path":"$tessdata$/ton.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3729371}
]} ]}
, "Turkish":{"files":[ , "Turkish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/tur.traineddata","https://translator.gres.biz/resources/tessdata_best/tur.traineddata.zip"], "path":"$tessdata$/tur.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7456265} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/tur.traineddata","https://translator.gres.biz/resources/tessdata_best/tur.traineddata.zip"], "path":"$tessdata$/tur.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7456265}
]} ]}
, "Uighur, Uyghur":{"files":[ , "Uighur, Uyghur":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/uig.traineddata","https://translator.gres.biz/resources/tessdata_best/uig.traineddata.zip"], "path":"$tessdata$/uig.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13074609} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/uig.traineddata","https://translator.gres.biz/resources/tessdata_best/uig.traineddata.zip"], "path":"$tessdata$/uig.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":13074609}
]} ]}
, "Ukrainian":{"files":[ , "Ukrainian":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/ukr.traineddata","https://translator.gres.biz/resources/tessdata_best/ukr.traineddata.zip"], "path":"$tessdata$/ukr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10859081} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/ukr.traineddata","https://translator.gres.biz/resources/tessdata_best/ukr.traineddata.zip"], "path":"$tessdata$/ukr.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":10859081}
]} ]}
, "Urdu":{"files":[ , "Urdu":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/urd.traineddata","https://translator.gres.biz/resources/tessdata_best/urd.traineddata.zip"], "path":"$tessdata$/urd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7994323} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/urd.traineddata","https://translator.gres.biz/resources/tessdata_best/urd.traineddata.zip"], "path":"$tessdata$/urd.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":7994323}
]} ]}
, "Uzbek":{"files":[ , "Uzbek":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/uzb.traineddata","https://translator.gres.biz/resources/tessdata_best/uzb.traineddata.zip"], "path":"$tessdata$/uzb.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12953454} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/uzb.traineddata","https://translator.gres.biz/resources/tessdata_best/uzb.traineddata.zip"], "path":"$tessdata$/uzb.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12953454}
]} ]}
, "uzb_cyrl":{"files":[ , "uzb_cyrl":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/uzb_cyrl.traineddata","https://translator.gres.biz/resources/tessdata_best/uzb_cyrl.traineddata.zip"], "path":"$tessdata$/uzb_cyrl.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4325478} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/uzb_cyrl.traineddata","https://translator.gres.biz/resources/tessdata_best/uzb_cyrl.traineddata.zip"], "path":"$tessdata$/uzb_cyrl.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":4325478}
]} ]}
, "Vietnamese":{"files":[ , "Vietnamese":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/vie.traineddata","https://translator.gres.biz/resources/tessdata_best/vie.traineddata.zip"], "path":"$tessdata$/vie.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12435550} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/vie.traineddata","https://translator.gres.biz/resources/tessdata_best/vie.traineddata.zip"], "path":"$tessdata$/vie.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":12435550}
]} ]}
, "Yiddish":{"files":[ , "Yiddish":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/yid.traineddata","https://translator.gres.biz/resources/tessdata_best/yid.traineddata.zip"], "path":"$tessdata$/yid.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3278995} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/yid.traineddata","https://translator.gres.biz/resources/tessdata_best/yid.traineddata.zip"], "path":"$tessdata$/yid.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3278995}
]} ]}
, "Yoruba":{"files":[ , "Yoruba":{"files":[
{"url":["https://github.com/tesseract-ocr/tessdata_best/raw/main/yor.traineddata","https://translator.gres.biz/resources/tessdata_best/yor.traineddata.zip"], "path":"$tessdata$/yor.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3736121} {"url":["https://github.com/tesseract-ocr/tessdata_best/raw/master/yor.traineddata","https://translator.gres.biz/resources/tessdata_best/yor.traineddata.zip"], "path":"$tessdata$/yor.traineddata", "date":"2020-03-09T08:28:45+01:00", "size":3736121}
]} ]}
} }
,"correction": { ,"hunspell": {
"Afrikaans":{"files":[ "Afrikaans":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/af_ZA/af_ZA.aff","https://translator.gres.biz/resources/dictionaries/af_ZA/af_ZA.aff.zip"], "path":"$hunspell$/af/af_ZA.aff", "date":"2020-02-16T20:22:16+01:00", "size":5027} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/af_ZA/af_ZA.aff","https://translator.gres.biz/resources/dictionaries/af_ZA/af_ZA.aff.zip"], "path":"$hunspell$/af/af_ZA.aff", "date":"2020-03-17T12:21:16+01:00", "size":5027}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/af_ZA/af_ZA.dic","https://translator.gres.biz/resources/dictionaries/af_ZA/af_ZA.dic.zip"], "path":"$hunspell$/af/af_ZA.dic", "date":"2020-02-16T20:22:16+01:00", "size":1262203} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/af_ZA/af_ZA.dic","https://translator.gres.biz/resources/dictionaries/af_ZA/af_ZA.dic.zip"], "path":"$hunspell$/af/af_ZA.dic", "date":"2020-03-17T12:21:16+01:00", "size":1262203}
]} ]}
, "Arabic":{"files":[ , "Arabic":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ar/ar.aff","https://translator.gres.biz/resources/dictionaries/ar/ar.aff.zip"], "path":"$hunspell$/ar/ar.aff", "date":"2018-02-04T21:34:12+01:00", "size":86949} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ar/ar.aff","https://translator.gres.biz/resources/dictionaries/ar/ar.aff.zip"], "path":"$hunspell$/ar/ar.aff", "date":"2020-03-17T12:21:16+01:00", "size":86949}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ar/ar.dic","https://translator.gres.biz/resources/dictionaries/ar/ar.dic.zip"], "path":"$hunspell$/ar/ar.dic", "date":"2019-03-07T11:32:58+01:00", "size":7217161} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ar/ar.dic","https://translator.gres.biz/resources/dictionaries/ar/ar.dic.zip"], "path":"$hunspell$/ar/ar.dic", "date":"2020-03-17T12:21:16+01:00", "size":7217161}
]} ]}
, "Belarusian":{"files":[ , "Belarusian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/be_BY/be-official.aff","https://translator.gres.biz/resources/dictionaries/be_BY/be-official.aff.zip"], "path":"$hunspell$/be/be-official.aff", "date":"2021-09-27T10:14:30+02:00", "size":183480} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/be_BY/be_BY.aff","https://translator.gres.biz/resources/dictionaries/be_BY/be_BY.aff.zip"], "path":"$hunspell$/be/be_BY.aff", "date":"2020-03-17T12:21:16+01:00", "size":23968}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/be_BY/be-official.dic","https://translator.gres.biz/resources/dictionaries/be_BY/be-official.dic.zip"], "path":"$hunspell$/be/be-official.dic", "date":"2021-09-27T10:14:30+02:00", "size":9355556} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/be_BY/be_BY.dic","https://translator.gres.biz/resources/dictionaries/be_BY/be_BY.dic.zip"], "path":"$hunspell$/be/be_BY.dic", "date":"2020-03-17T12:21:16+01:00", "size":1707840}
]} ]}
, "Bulgarian":{"files":[ , "Bulgarian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bg_BG/bg_BG.aff","https://translator.gres.biz/resources/dictionaries/bg_BG/bg_BG.aff.zip"], "path":"$hunspell$/bg/bg_BG.aff", "date":"2018-06-29T12:25:29+02:00", "size":58189} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bg_BG/bg_BG.aff","https://translator.gres.biz/resources/dictionaries/bg_BG/bg_BG.aff.zip"], "path":"$hunspell$/bg/bg_BG.aff", "date":"2020-03-17T12:21:16+01:00", "size":58189}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bg_BG/bg_BG.dic","https://translator.gres.biz/resources/dictionaries/bg_BG/bg_BG.dic.zip"], "path":"$hunspell$/bg/bg_BG.dic", "date":"2018-06-29T12:25:29+02:00", "size":1566331} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bg_BG/bg_BG.dic","https://translator.gres.biz/resources/dictionaries/bg_BG/bg_BG.dic.zip"], "path":"$hunspell$/bg/bg_BG.dic", "date":"2020-03-17T12:21:16+01:00", "size":1566331}
]} ]}
, "Bengali":{"files":[ , "Bengali":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bn_BD/bn_BD.aff","https://translator.gres.biz/resources/dictionaries/bn_BD/bn_BD.aff.zip"], "path":"$hunspell$/bn/bn_BD.aff", "date":"2012-10-16T11:09:27-05:00", "size":195} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bn_BD/bn_BD.aff","https://translator.gres.biz/resources/dictionaries/bn_BD/bn_BD.aff.zip"], "path":"$hunspell$/bn/bn_BD.aff", "date":"2020-03-17T12:21:16+01:00", "size":195}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bn_BD/bn_BD.dic","https://translator.gres.biz/resources/dictionaries/bn_BD/bn_BD.dic.zip"], "path":"$hunspell$/bn/bn_BD.dic", "date":"2012-10-16T11:09:27-05:00", "size":2596038} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bn_BD/bn_BD.dic","https://translator.gres.biz/resources/dictionaries/bn_BD/bn_BD.dic.zip"], "path":"$hunspell$/bn/bn_BD.dic", "date":"2020-03-17T12:21:16+01:00", "size":2596038}
]} ]}
, "Tibetan":{"files":[ , "Tibetan":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bo/bo.aff","https://translator.gres.biz/resources/dictionaries/bo/bo.aff.zip"], "path":"$hunspell$/bo/bo.aff", "date":"2016-11-22T22:23:34+00:00", "size":1706} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bo/bo.aff","https://translator.gres.biz/resources/dictionaries/bo/bo.aff.zip"], "path":"$hunspell$/bo/bo.aff", "date":"2020-03-17T12:21:16+01:00", "size":1706}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bo/bo.dic","https://translator.gres.biz/resources/dictionaries/bo/bo.dic.zip"], "path":"$hunspell$/bo/bo.dic", "date":"2017-10-23T18:37:13+02:00", "size":4637} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bo/bo.dic","https://translator.gres.biz/resources/dictionaries/bo/bo.dic.zip"], "path":"$hunspell$/bo/bo.dic", "date":"2020-03-17T12:21:16+01:00", "size":4637}
]} ]}
, "Bosnian":{"files":[ , "Bosnian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bs_BA/bs_BA.aff","https://translator.gres.biz/resources/dictionaries/bs_BA/bs_BA.aff.zip"], "path":"$hunspell$/bs/bs_BA.aff", "date":"2013-01-22T17:32:09+01:00", "size":17468} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bs_BA/bs_BA.aff","https://translator.gres.biz/resources/dictionaries/bs_BA/bs_BA.aff.zip"], "path":"$hunspell$/bs/bs_BA.aff", "date":"2020-03-17T12:21:16+01:00", "size":17468}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bs_BA/bs_BA.dic","https://translator.gres.biz/resources/dictionaries/bs_BA/bs_BA.dic.zip"], "path":"$hunspell$/bs/bs_BA.dic", "date":"2013-01-22T17:32:09+01:00", "size":339863} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/bs_BA/bs_BA.dic","https://translator.gres.biz/resources/dictionaries/bs_BA/bs_BA.dic.zip"], "path":"$hunspell$/bs/bs_BA.dic", "date":"2020-03-17T12:21:16+01:00", "size":339863}
]} ]}
, "Czech":{"files":[ , "Czech":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/cs_CZ/cs_CZ.aff","https://translator.gres.biz/resources/dictionaries/cs_CZ/cs_CZ.aff.zip"], "path":"$hunspell$/cs/cs_CZ.aff", "date":"2021-07-01T19:25:44+02:00", "size":111575} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/cs_CZ/cs_CZ.aff","https://translator.gres.biz/resources/dictionaries/cs_CZ/cs_CZ.aff.zip"], "path":"$hunspell$/cs/cs_CZ.aff", "date":"2020-03-17T12:21:16+01:00", "size":97286}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/cs_CZ/cs_CZ.dic","https://translator.gres.biz/resources/dictionaries/cs_CZ/cs_CZ.dic.zip"], "path":"$hunspell$/cs/cs_CZ.dic", "date":"2021-07-28T19:02:59+02:00", "size":3656362} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/cs_CZ/cs_CZ.dic","https://translator.gres.biz/resources/dictionaries/cs_CZ/cs_CZ.dic.zip"], "path":"$hunspell$/cs/cs_CZ.dic", "date":"2020-03-17T12:21:16+01:00", "size":2209232}
]} ]}
, "Danish":{"files":[ , "Danish":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/da_DK/da_DK.aff","https://translator.gres.biz/resources/dictionaries/da_DK/da_DK.aff.zip"], "path":"$hunspell$/da/da_DK.aff", "date":"2022-06-09T11:42:30+02:00", "size":79054} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/da_DK/da_DK.aff","https://translator.gres.biz/resources/dictionaries/da_DK/da_DK.aff.zip"], "path":"$hunspell$/da/da_DK.aff", "date":"2020-03-17T12:21:16+01:00", "size":55779}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/da_DK/da_DK.dic","https://translator.gres.biz/resources/dictionaries/da_DK/da_DK.dic.zip"], "path":"$hunspell$/da/da_DK.dic", "date":"2022-06-09T11:42:30+02:00", "size":3514463} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/da_DK/da_DK.dic","https://translator.gres.biz/resources/dictionaries/da_DK/da_DK.dic.zip"], "path":"$hunspell$/da/da_DK.dic", "date":"2020-03-17T12:21:16+01:00", "size":2915525}
]} ]}
, "German":{"files":[ , "German":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/de/de_DE_frami.aff","https://translator.gres.biz/resources/dictionaries/de/de_DE_frami.aff.zip"], "path":"$hunspell$/de/de_DE_frami.aff", "date":"2022-09-23T10:52:56+02:00", "size":19067} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/de/de_DE_frami.aff","https://translator.gres.biz/resources/dictionaries/de/de_DE_frami.aff.zip"], "path":"$hunspell$/de/de_DE_frami.aff", "date":"2020-03-17T12:21:16+01:00", "size":18991}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/de/de_DE_frami.dic","https://translator.gres.biz/resources/dictionaries/de/de_DE_frami.dic.zip"], "path":"$hunspell$/de/de_DE_frami.dic", "date":"2017-01-22T19:03:05+00:00", "size":4356858} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/de/de_DE_frami.dic","https://translator.gres.biz/resources/dictionaries/de/de_DE_frami.dic.zip"], "path":"$hunspell$/de/de_DE_frami.dic", "date":"2020-03-17T12:21:16+01:00", "size":4356858}
]} ]}
, "Greek":{"files":[ , "Greek":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/el_GR/el_GR.aff","https://translator.gres.biz/resources/dictionaries/el_GR/el_GR.aff.zip"], "path":"$hunspell$/el/el_GR.aff", "date":"2015-09-21T17:56:43+02:00", "size":15647} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/el_GR/el_GR.aff","https://translator.gres.biz/resources/dictionaries/el_GR/el_GR.aff.zip"], "path":"$hunspell$/el/el_GR.aff", "date":"2020-03-17T12:21:16+01:00", "size":15647}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/el_GR/el_GR.dic","https://translator.gres.biz/resources/dictionaries/el_GR/el_GR.dic.zip"], "path":"$hunspell$/el/el_GR.dic", "date":"2015-09-21T17:56:43+02:00", "size":10125390} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/el_GR/el_GR.dic","https://translator.gres.biz/resources/dictionaries/el_GR/el_GR.dic.zip"], "path":"$hunspell$/el/el_GR.dic", "date":"2020-03-17T12:21:16+01:00", "size":10125390}
]} ]}
, "English":{"files":[ , "English":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.aff","https://translator.gres.biz/resources/dictionaries/en/en_US.aff.zip"], "path":"$hunspell$/en/en_US.aff", "date":"2018-05-15T00:49:14+02:00", "size":3090} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.aff","https://translator.gres.biz/resources/dictionaries/en/en_US.aff.zip"], "path":"$hunspell$/en/en_US.aff", "date":"2020-03-17T12:21:16+01:00", "size":3090}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.dic","https://translator.gres.biz/resources/dictionaries/en/en_US.dic.zip"], "path":"$hunspell$/en/en_US.dic", "date":"2021-05-12T15:36:00+02:00", "size":551762} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.dic","https://translator.gres.biz/resources/dictionaries/en/en_US.dic.zip"], "path":"$hunspell$/en/en_US.dic", "date":"2020-03-17T12:21:16+01:00", "size":551260}
]} ]}
, "Esperanto":{"files":[ , "Spanish":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/eo/eo.aff","https://translator.gres.biz/resources/dictionaries/eo/eo.aff.zip"], "path":"$hunspell$/eo/eo.aff", "date":"2021-04-11T10:01:47+02:00", "size":19129} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/es/es_ANY.aff","https://translator.gres.biz/resources/dictionaries/es/es_ANY.aff.zip"], "path":"$hunspell$/es/es_ANY.aff", "date":"2020-03-17T12:21:16+01:00", "size":169377}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/eo/eo.dic","https://translator.gres.biz/resources/dictionaries/eo/eo.dic.zip"], "path":"$hunspell$/eo/eo.dic", "date":"2021-04-11T10:01:47+02:00", "size":377989} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/es/es_ANY.dic","https://translator.gres.biz/resources/dictionaries/es/es_ANY.dic.zip"], "path":"$hunspell$/es/es_ANY.dic", "date":"2020-03-17T12:21:16+01:00", "size":804058}
]} ]}
, "Estonian":{"files":[ , "Estonian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/et_EE/et_EE.aff","https://translator.gres.biz/resources/dictionaries/et_EE/et_EE.aff.zip"], "path":"$hunspell$/et/et_EE.aff", "date":"2012-10-16T11:09:27-05:00", "size":236336} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/et_EE/et_EE.aff","https://translator.gres.biz/resources/dictionaries/et_EE/et_EE.aff.zip"], "path":"$hunspell$/et/et_EE.aff", "date":"2020-03-17T12:21:16+01:00", "size":236336}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/et_EE/et_EE.dic","https://translator.gres.biz/resources/dictionaries/et_EE/et_EE.dic.zip"], "path":"$hunspell$/et/et_EE.dic", "date":"2012-10-16T11:09:27-05:00", "size":4383841} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/et_EE/et_EE.dic","https://translator.gres.biz/resources/dictionaries/et_EE/et_EE.dic.zip"], "path":"$hunspell$/et/et_EE.dic", "date":"2020-03-17T12:21:16+01:00", "size":4383841}
]}
, "Persian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/fa_IR/fa-IR.aff","https://translator.gres.biz/resources/dictionaries/fa_IR/fa-IR.aff.zip"], "path":"$hunspell$/fa/fa-IR.aff", "date":"2022-08-27T17:55:37+02:00", "size":5439}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/fa_IR/fa-IR.dic","https://translator.gres.biz/resources/dictionaries/fa_IR/fa-IR.dic.zip"], "path":"$hunspell$/fa/fa-IR.dic", "date":"2022-08-27T17:55:37+02:00", "size":2575990}
]} ]}
, "French":{"files":[ , "French":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/fr_FR/fr.aff","https://translator.gres.biz/resources/dictionaries/fr_FR/fr.aff.zip"], "path":"$hunspell$/fr/fr.aff", "date":"2020-12-22T09:23:57+01:00", "size":201591} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/fr_FR/fr.aff","https://translator.gres.biz/resources/dictionaries/fr_FR/fr.aff.zip"], "path":"$hunspell$/fr/fr.aff", "date":"2020-03-17T12:21:16+01:00", "size":256857}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/fr_FR/fr.dic","https://translator.gres.biz/resources/dictionaries/fr_FR/fr.dic.zip"], "path":"$hunspell$/fr/fr.dic", "date":"2020-12-22T09:23:57+01:00", "size":1227095} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/fr_FR/fr.dic","https://translator.gres.biz/resources/dictionaries/fr_FR/fr.dic.zip"], "path":"$hunspell$/fr/fr.dic", "date":"2020-03-17T12:21:16+01:00", "size":1100397}
]} ]}
, "Gaelic":{"files":[ , "Gaelic":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gd_GB/gd_GB.aff","https://translator.gres.biz/resources/dictionaries/gd_GB/gd_GB.aff.zip"], "path":"$hunspell$/gd/gd_GB.aff", "date":"2017-06-22T00:27:25+02:00", "size":8228} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gd_GB/gd_GB.aff","https://translator.gres.biz/resources/dictionaries/gd_GB/gd_GB.aff.zip"], "path":"$hunspell$/gd/gd_GB.aff", "date":"2020-03-17T12:21:16+01:00", "size":8228}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gd_GB/gd_GB.dic","https://translator.gres.biz/resources/dictionaries/gd_GB/gd_GB.dic.zip"], "path":"$hunspell$/gd/gd_GB.dic", "date":"2017-06-22T00:27:25+02:00", "size":4806682} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gd_GB/gd_GB.dic","https://translator.gres.biz/resources/dictionaries/gd_GB/gd_GB.dic.zip"], "path":"$hunspell$/gd/gd_GB.dic", "date":"2020-03-17T12:21:16+01:00", "size":4806682}
]} ]}
, "Galician":{"files":[ , "Galician":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gl/gl_ES.aff","https://translator.gres.biz/resources/dictionaries/gl/gl_ES.aff.zip"], "path":"$hunspell$/gl/gl_ES.aff", "date":"2021-07-26T16:31:04+02:00", "size":1163541} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gl/gl_ES.aff","https://translator.gres.biz/resources/dictionaries/gl/gl_ES.aff.zip"], "path":"$hunspell$/gl/gl_ES.aff", "date":"2020-03-17T12:21:16+01:00", "size":1159910}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gl/gl_ES.dic","https://translator.gres.biz/resources/dictionaries/gl/gl_ES.dic.zip"], "path":"$hunspell$/gl/gl_ES.dic", "date":"2021-07-26T16:31:04+02:00", "size":8325262} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gl/gl_ES.dic","https://translator.gres.biz/resources/dictionaries/gl/gl_ES.dic.zip"], "path":"$hunspell$/gl/gl_ES.dic", "date":"2020-03-17T12:21:16+01:00", "size":8636406}
]} ]}
, "Gujarati":{"files":[ , "Gujarati":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gu_IN/gu_IN.aff","https://translator.gres.biz/resources/dictionaries/gu_IN/gu_IN.aff.zip"], "path":"$hunspell$/gu/gu_IN.aff", "date":"2012-10-16T11:09:27-05:00", "size":174} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gu_IN/gu_IN.aff","https://translator.gres.biz/resources/dictionaries/gu_IN/gu_IN.aff.zip"], "path":"$hunspell$/gu/gu_IN.aff", "date":"2020-03-17T12:21:16+01:00", "size":174}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gu_IN/gu_IN.dic","https://translator.gres.biz/resources/dictionaries/gu_IN/gu_IN.dic.zip"], "path":"$hunspell$/gu/gu_IN.dic", "date":"2012-10-16T11:09:27-05:00", "size":3792870} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/gu_IN/gu_IN.dic","https://translator.gres.biz/resources/dictionaries/gu_IN/gu_IN.dic.zip"], "path":"$hunspell$/gu/gu_IN.dic", "date":"2020-03-17T12:21:16+01:00", "size":3792870}
]} ]}
, "Hebrew":{"files":[ , "Hebrew":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/he_IL/he_IL.aff","https://translator.gres.biz/resources/dictionaries/he_IL/he_IL.aff.zip"], "path":"$hunspell$/he/he_IL.aff", "date":"2017-09-05T18:11:31+02:00", "size":78883} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/he_IL/he_IL.aff","https://translator.gres.biz/resources/dictionaries/he_IL/he_IL.aff.zip"], "path":"$hunspell$/he/he_IL.aff", "date":"2020-03-17T12:21:16+01:00", "size":78883}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/he_IL/he_IL.dic","https://translator.gres.biz/resources/dictionaries/he_IL/he_IL.dic.zip"], "path":"$hunspell$/he/he_IL.dic", "date":"2017-09-05T18:11:31+02:00", "size":7796259} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/he_IL/he_IL.dic","https://translator.gres.biz/resources/dictionaries/he_IL/he_IL.dic.zip"], "path":"$hunspell$/he/he_IL.dic", "date":"2020-03-17T12:21:16+01:00", "size":7796259}
]} ]}
, "Hindi":{"files":[ , "Hindi":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hi_IN/hi_IN.aff","https://translator.gres.biz/resources/dictionaries/hi_IN/hi_IN.aff.zip"], "path":"$hunspell$/hi/hi_IN.aff", "date":"2012-10-16T11:09:27-05:00", "size":210} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hi_IN/hi_IN.aff","https://translator.gres.biz/resources/dictionaries/hi_IN/hi_IN.aff.zip"], "path":"$hunspell$/hi/hi_IN.aff", "date":"2020-03-17T12:21:16+01:00", "size":210}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hi_IN/hi_IN.dic","https://translator.gres.biz/resources/dictionaries/hi_IN/hi_IN.dic.zip"], "path":"$hunspell$/hi/hi_IN.dic", "date":"2012-10-16T11:09:27-05:00", "size":303963} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hi_IN/hi_IN.dic","https://translator.gres.biz/resources/dictionaries/hi_IN/hi_IN.dic.zip"], "path":"$hunspell$/hi/hi_IN.dic", "date":"2020-03-17T12:21:16+01:00", "size":303963}
]} ]}
, "Croatian":{"files":[ , "Croatian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hr_HR/hr_HR.aff","https://translator.gres.biz/resources/dictionaries/hr_HR/hr_HR.aff.zip"], "path":"$hunspell$/hr/hr_HR.aff", "date":"2018-05-29T22:11:06+02:00", "size":95802} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hr_HR/hr_HR.aff","https://translator.gres.biz/resources/dictionaries/hr_HR/hr_HR.aff.zip"], "path":"$hunspell$/hr/hr_HR.aff", "date":"2020-03-17T12:21:16+01:00", "size":95802}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hr_HR/hr_HR.dic","https://translator.gres.biz/resources/dictionaries/hr_HR/hr_HR.dic.zip"], "path":"$hunspell$/hr/hr_HR.dic", "date":"2018-05-29T22:11:06+02:00", "size":731819} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hr_HR/hr_HR.dic","https://translator.gres.biz/resources/dictionaries/hr_HR/hr_HR.dic.zip"], "path":"$hunspell$/hr/hr_HR.dic", "date":"2020-03-17T12:21:16+01:00", "size":731819}
]} ]}
, "Hungarian":{"files":[ , "Hungarian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hu_HU/hu_HU.aff","https://translator.gres.biz/resources/dictionaries/hu_HU/hu_HU.aff.zip"], "path":"$hunspell$/hu/hu_HU.aff", "date":"2018-05-22T22:26:58+02:00", "size":2106214} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hu_HU/hu_HU.aff","https://translator.gres.biz/resources/dictionaries/hu_HU/hu_HU.aff.zip"], "path":"$hunspell$/hu/hu_HU.aff", "date":"2020-03-17T12:21:16+01:00", "size":2106214}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hu_HU/hu_HU.dic","https://translator.gres.biz/resources/dictionaries/hu_HU/hu_HU.dic.zip"], "path":"$hunspell$/hu/hu_HU.dic", "date":"2018-05-22T22:26:58+02:00", "size":1653155} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/hu_HU/hu_HU.dic","https://translator.gres.biz/resources/dictionaries/hu_HU/hu_HU.dic.zip"], "path":"$hunspell$/hu/hu_HU.dic", "date":"2020-03-17T12:21:16+01:00", "size":1653155}
]} ]}
, "Indonesian":{"files":[ , "Indonesian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/id/id_ID.aff","https://translator.gres.biz/resources/dictionaries/id/id_ID.aff.zip"], "path":"$hunspell$/id/id_ID.aff", "date":"2018-02-28T01:40:08+01:00", "size":14957} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/id/id_ID.aff","https://translator.gres.biz/resources/dictionaries/id/id_ID.aff.zip"], "path":"$hunspell$/id/id_ID.aff", "date":"2020-03-17T12:21:16+01:00", "size":14957}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/id/id_ID.dic","https://translator.gres.biz/resources/dictionaries/id/id_ID.dic.zip"], "path":"$hunspell$/id/id_ID.dic", "date":"2018-02-28T01:40:08+01:00", "size":315384} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/id/id_ID.dic","https://translator.gres.biz/resources/dictionaries/id/id_ID.dic.zip"], "path":"$hunspell$/id/id_ID.dic", "date":"2020-03-17T12:21:16+01:00", "size":315384}
]} ]}
, "Icelandic":{"files":[ , "Icelandic":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/is/is.aff","https://translator.gres.biz/resources/dictionaries/is/is.aff.zip"], "path":"$hunspell$/is/is.aff", "date":"2016-03-14T09:05:09+00:00", "size":309734} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/is/is.aff","https://translator.gres.biz/resources/dictionaries/is/is.aff.zip"], "path":"$hunspell$/is/is.aff", "date":"2020-03-17T12:21:16+01:00", "size":309734}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/is/is.dic","https://translator.gres.biz/resources/dictionaries/is/is.dic.zip"], "path":"$hunspell$/is/is.dic", "date":"2016-03-14T09:05:09+00:00", "size":2454138} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/is/is.dic","https://translator.gres.biz/resources/dictionaries/is/is.dic.zip"], "path":"$hunspell$/is/is.dic", "date":"2020-03-17T12:21:16+01:00", "size":2454138}
]} ]}
, "Italian":{"files":[ , "Italian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/it_IT/it_IT.aff","https://translator.gres.biz/resources/dictionaries/it_IT/it_IT.aff.zip"], "path":"$hunspell$/it/it_IT.aff", "date":"2020-10-28T10:37:21+01:00", "size":70054} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/it_IT/it_IT.aff","https://translator.gres.biz/resources/dictionaries/it_IT/it_IT.aff.zip"], "path":"$hunspell$/it/it_IT.aff", "date":"2020-03-17T12:21:16+01:00", "size":80216}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/it_IT/it_IT.dic","https://translator.gres.biz/resources/dictionaries/it_IT/it_IT.dic.zip"], "path":"$hunspell$/it/it_IT.dic", "date":"2021-01-20T09:47:03+01:00", "size":1295078} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/it_IT/it_IT.dic","https://translator.gres.biz/resources/dictionaries/it_IT/it_IT.dic.zip"], "path":"$hunspell$/it/it_IT.dic", "date":"2020-03-17T12:21:16+01:00", "size":1290681}
]}
, "Korean":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ko_KR/ko_KR.aff","https://translator.gres.biz/resources/dictionaries/ko_KR/ko_KR.aff.zip"], "path":"$hunspell$/ko/ko_KR.aff", "date":"2020-10-28T10:46:18+01:00", "size":11094418}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ko_KR/ko_KR.dic","https://translator.gres.biz/resources/dictionaries/ko_KR/ko_KR.dic.zip"], "path":"$hunspell$/ko/ko_KR.dic", "date":"2020-10-28T10:46:18+01:00", "size":2862610}
]} ]}
, "Lao":{"files":[ , "Lao":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lo_LA/lo_LA.aff","https://translator.gres.biz/resources/dictionaries/lo_LA/lo_LA.aff.zip"], "path":"$hunspell$/lo/lo_LA.aff", "date":"2013-11-24T19:21:08+01:00", "size":10} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lo_LA/lo_LA.aff","https://translator.gres.biz/resources/dictionaries/lo_LA/lo_LA.aff.zip"], "path":"$hunspell$/lo/lo_LA.aff", "date":"2020-03-17T12:21:16+01:00", "size":10}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lo_LA/lo_LA.dic","https://translator.gres.biz/resources/dictionaries/lo_LA/lo_LA.dic.zip"], "path":"$hunspell$/lo/lo_LA.dic", "date":"2021-05-11T15:56:42+02:00", "size":671495} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lo_LA/lo_LA.dic","https://translator.gres.biz/resources/dictionaries/lo_LA/lo_LA.dic.zip"], "path":"$hunspell$/lo/lo_LA.dic", "date":"2020-03-17T12:21:16+01:00", "size":203209}
]} ]}
, "Lithuanian":{"files":[ , "Lithuanian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lt_LT/lt.aff","https://translator.gres.biz/resources/dictionaries/lt_LT/lt.aff.zip"], "path":"$hunspell$/lt/lt.aff", "date":"2013-01-23T11:35:37+00:00", "size":92208} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lt_LT/lt.aff","https://translator.gres.biz/resources/dictionaries/lt_LT/lt.aff.zip"], "path":"$hunspell$/lt/lt.aff", "date":"2020-03-17T12:21:16+01:00", "size":92208}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lt_LT/lt.dic","https://translator.gres.biz/resources/dictionaries/lt_LT/lt.dic.zip"], "path":"$hunspell$/lt/lt.dic", "date":"2013-01-23T11:35:37+00:00", "size":1085291} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lt_LT/lt.dic","https://translator.gres.biz/resources/dictionaries/lt_LT/lt.dic.zip"], "path":"$hunspell$/lt/lt.dic", "date":"2020-03-17T12:21:16+01:00", "size":1085291}
]} ]}
, "Latvian":{"files":[ , "Latvian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lv_LV/lv_LV.aff","https://translator.gres.biz/resources/dictionaries/lv_LV/lv_LV.aff.zip"], "path":"$hunspell$/lv/lv_LV.aff", "date":"2020-05-24T12:13:08+02:00", "size":130475} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lv_LV/lv_LV.aff","https://translator.gres.biz/resources/dictionaries/lv_LV/lv_LV.aff.zip"], "path":"$hunspell$/lv/lv_LV.aff", "date":"2020-03-17T12:21:16+01:00", "size":69597}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lv_LV/lv_LV.dic","https://translator.gres.biz/resources/dictionaries/lv_LV/lv_LV.dic.zip"], "path":"$hunspell$/lv/lv_LV.dic", "date":"2020-05-24T12:13:08+02:00", "size":1844831} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/lv_LV/lv_LV.dic","https://translator.gres.biz/resources/dictionaries/lv_LV/lv_LV.dic.zip"], "path":"$hunspell$/lv/lv_LV.dic", "date":"2020-03-17T12:21:16+01:00", "size":2226834}
]}
, "Mongolian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/mn_MN/mn_MN.aff","https://translator.gres.biz/resources/dictionaries/mn_MN/mn_MN.aff.zip"], "path":"$hunspell$/mn/mn_MN.aff", "date":"2022-04-18T07:06:27+02:00", "size":398455}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/mn_MN/mn_MN.dic","https://translator.gres.biz/resources/dictionaries/mn_MN/mn_MN.dic.zip"], "path":"$hunspell$/mn/mn_MN.dic", "date":"2022-04-18T07:06:27+02:00", "size":16650918}
]} ]}
, "Nepali":{"files":[ , "Nepali":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ne_NP/ne_NP.aff","https://translator.gres.biz/resources/dictionaries/ne_NP/ne_NP.aff.zip"], "path":"$hunspell$/ne/ne_NP.aff", "date":"2012-10-16T11:09:27-05:00", "size":14162} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ne_NP/ne_NP.aff","https://translator.gres.biz/resources/dictionaries/ne_NP/ne_NP.aff.zip"], "path":"$hunspell$/ne/ne_NP.aff", "date":"2020-03-17T12:21:16+01:00", "size":14162}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ne_NP/ne_NP.dic","https://translator.gres.biz/resources/dictionaries/ne_NP/ne_NP.dic.zip"], "path":"$hunspell$/ne/ne_NP.dic", "date":"2012-10-16T11:09:27-05:00", "size":874372} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ne_NP/ne_NP.dic","https://translator.gres.biz/resources/dictionaries/ne_NP/ne_NP.dic.zip"], "path":"$hunspell$/ne/ne_NP.dic", "date":"2020-03-17T12:21:16+01:00", "size":874372}
]} ]}
, "Dutch":{"files":[ , "Dutch":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/nl_NL/nl_NL.aff","https://translator.gres.biz/resources/dictionaries/nl_NL/nl_NL.aff.zip"], "path":"$hunspell$/nl/nl_NL.aff", "date":"2013-07-22T17:41:01+00:00", "size":27835} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/nl_NL/nl_NL.aff","https://translator.gres.biz/resources/dictionaries/nl_NL/nl_NL.aff.zip"], "path":"$hunspell$/nl/nl_NL.aff", "date":"2020-03-17T12:21:16+01:00", "size":27835}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/nl_NL/nl_NL.dic","https://translator.gres.biz/resources/dictionaries/nl_NL/nl_NL.dic.zip"], "path":"$hunspell$/nl/nl_NL.dic", "date":"2013-07-22T17:41:01+00:00", "size":1881063} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/nl_NL/nl_NL.dic","https://translator.gres.biz/resources/dictionaries/nl_NL/nl_NL.dic.zip"], "path":"$hunspell$/nl/nl_NL.dic", "date":"2020-03-17T12:21:16+01:00", "size":1881063}
]} ]}
, "Norwegian":{"files":[ , "Norwegian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/no/nb_NO.aff","https://translator.gres.biz/resources/dictionaries/no/nb_NO.aff.zip"], "path":"$hunspell$/no/nb_NO.aff", "date":"2013-05-23T11:54:36+01:00", "size":17259} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/no/nb_NO.aff","https://translator.gres.biz/resources/dictionaries/no/nb_NO.aff.zip"], "path":"$hunspell$/no/nb_NO.aff", "date":"2020-03-17T12:21:16+01:00", "size":17259}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/no/nb_NO.dic","https://translator.gres.biz/resources/dictionaries/no/nb_NO.dic.zip"], "path":"$hunspell$/no/nb_NO.dic", "date":"2018-09-05T10:30:32+02:00", "size":5274030} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/no/nb_NO.dic","https://translator.gres.biz/resources/dictionaries/no/nb_NO.dic.zip"], "path":"$hunspell$/no/nb_NO.dic", "date":"2020-03-17T12:21:16+01:00", "size":5274030}
]} ]}
, "Polish":{"files":[ , "Polish":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pl_PL/pl_PL.aff","https://translator.gres.biz/resources/dictionaries/pl_PL/pl_PL.aff.zip"], "path":"$hunspell$/pl/pl_PL.aff", "date":"2017-05-05T15:26:38+02:00", "size":246842} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pl_PL/pl_PL.aff","https://translator.gres.biz/resources/dictionaries/pl_PL/pl_PL.aff.zip"], "path":"$hunspell$/pl/pl_PL.aff", "date":"2020-03-17T12:21:16+01:00", "size":246842}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pl_PL/pl_PL.dic","https://translator.gres.biz/resources/dictionaries/pl_PL/pl_PL.dic.zip"], "path":"$hunspell$/pl/pl_PL.dic", "date":"2017-05-21T10:58:59+02:00", "size":4539105} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pl_PL/pl_PL.dic","https://translator.gres.biz/resources/dictionaries/pl_PL/pl_PL.dic.zip"], "path":"$hunspell$/pl/pl_PL.dic", "date":"2020-03-17T12:21:16+01:00", "size":4539105}
]} ]}
, "Portuguese":{"files":[ , "Portuguese":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pt_BR/pt_BR.aff","https://translator.gres.biz/resources/dictionaries/pt_BR/pt_BR.aff.zip"], "path":"$hunspell$/pt/pt_BR.aff", "date":"2021-11-12T14:13:08+01:00", "size":979792} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pt_PT/pt_PT.aff","https://translator.gres.biz/resources/dictionaries/pt_PT/pt_PT.aff.zip"], "path":"$hunspell$/pt/pt_PT.aff", "date":"2020-03-17T12:21:16+01:00", "size":95089}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pt_BR/pt_BR.dic","https://translator.gres.biz/resources/dictionaries/pt_BR/pt_BR.dic.zip"], "path":"$hunspell$/pt/pt_BR.dic", "date":"2021-11-12T14:13:08+01:00", "size":4477695} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/pt_PT/pt_PT.dic","https://translator.gres.biz/resources/dictionaries/pt_PT/pt_PT.dic.zip"], "path":"$hunspell$/pt/pt_PT.dic", "date":"2020-03-17T12:21:16+01:00", "size":1473077}
]} ]}
, "Romanian":{"files":[ , "Romanian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ro/ro_RO.aff","https://translator.gres.biz/resources/dictionaries/ro/ro_RO.aff.zip"], "path":"$hunspell$/ro/ro_RO.aff", "date":"2013-03-28T11:26:45+01:00", "size":55181} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ro/ro_RO.aff","https://translator.gres.biz/resources/dictionaries/ro/ro_RO.aff.zip"], "path":"$hunspell$/ro/ro_RO.aff", "date":"2020-03-17T12:21:16+01:00", "size":55181}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ro/ro_RO.dic","https://translator.gres.biz/resources/dictionaries/ro/ro_RO.dic.zip"], "path":"$hunspell$/ro/ro_RO.dic", "date":"2013-03-28T11:26:45+01:00", "size":2196348} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ro/ro_RO.dic","https://translator.gres.biz/resources/dictionaries/ro/ro_RO.dic.zip"], "path":"$hunspell$/ro/ro_RO.dic", "date":"2020-03-17T12:21:16+01:00", "size":2196348}
]} ]}
, "Russian":{"files":[ , "Russian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.aff","https://translator.gres.biz/resources/dictionaries/ru_RU/ru_RU.aff.zip"], "path":"$hunspell$/ru/ru_RU.aff", "date":"2020-06-04T15:36:15+02:00", "size":71236} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.aff","https://translator.gres.biz/resources/dictionaries/ru_RU/ru_RU.aff.zip"], "path":"$hunspell$/ru/ru_RU.aff", "date":"2020-03-17T12:21:16+01:00", "size":53019}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.dic","https://translator.gres.biz/resources/dictionaries/ru_RU/ru_RU.dic.zip"], "path":"$hunspell$/ru/ru_RU.dic", "date":"2021-07-27T15:41:55+02:00", "size":3473191} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.dic","https://translator.gres.biz/resources/dictionaries/ru_RU/ru_RU.dic.zip"], "path":"$hunspell$/ru/ru_RU.dic", "date":"2020-03-17T12:21:16+01:00", "size":1969349}
]} ]}
, "Slovak":{"files":[ , "Slovak":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sk_SK/sk_SK.aff","https://translator.gres.biz/resources/dictionaries/sk_SK/sk_SK.aff.zip"], "path":"$hunspell$/sk/sk_SK.aff", "date":"2020-06-10T20:31:32+02:00", "size":195963} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sk_SK/sk_SK.aff","https://translator.gres.biz/resources/dictionaries/sk_SK/sk_SK.aff.zip"], "path":"$hunspell$/sk/sk_SK.aff", "date":"2020-03-17T12:21:16+01:00", "size":99414}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sk_SK/sk_SK.dic","https://translator.gres.biz/resources/dictionaries/sk_SK/sk_SK.dic.zip"], "path":"$hunspell$/sk/sk_SK.dic", "date":"2020-06-10T20:31:32+02:00", "size":4308934} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sk_SK/sk_SK.dic","https://translator.gres.biz/resources/dictionaries/sk_SK/sk_SK.dic.zip"], "path":"$hunspell$/sk/sk_SK.dic", "date":"2020-03-17T12:21:16+01:00", "size":3289769}
]} ]}
, "Slovenian":{"files":[ , "Slovenian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sl_SI/sl_SI.aff","https://translator.gres.biz/resources/dictionaries/sl_SI/sl_SI.aff.zip"], "path":"$hunspell$/sl/sl_SI.aff", "date":"2012-10-16T11:09:27-05:00", "size":14730} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sl_SI/sl_SI.aff","https://translator.gres.biz/resources/dictionaries/sl_SI/sl_SI.aff.zip"], "path":"$hunspell$/sl/sl_SI.aff", "date":"2020-03-17T12:21:16+01:00", "size":14730}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sl_SI/sl_SI.dic","https://translator.gres.biz/resources/dictionaries/sl_SI/sl_SI.dic.zip"], "path":"$hunspell$/sl/sl_SI.dic", "date":"2012-10-16T11:09:27-05:00", "size":2967766} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sl_SI/sl_SI.dic","https://translator.gres.biz/resources/dictionaries/sl_SI/sl_SI.dic.zip"], "path":"$hunspell$/sl/sl_SI.dic", "date":"2020-03-17T12:21:16+01:00", "size":2967766}
]} ]}
, "Albanian":{"files":[ , "Albanian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sq_AL/sq_AL.aff","https://translator.gres.biz/resources/dictionaries/sq_AL/sq_AL.aff.zip"], "path":"$hunspell$/sq/sq_AL.aff", "date":"2021-01-08T00:11:15+01:00", "size":7764} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sq_AL/sq_AL.aff","https://translator.gres.biz/resources/dictionaries/sq_AL/sq_AL.aff.zip"], "path":"$hunspell$/sq/sq_AL.aff", "date":"2020-03-17T12:21:16+01:00", "size":7555}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sq_AL/sq_AL.dic","https://translator.gres.biz/resources/dictionaries/sq_AL/sq_AL.dic.zip"], "path":"$hunspell$/sq/sq_AL.dic", "date":"2021-01-08T00:11:15+01:00", "size":2726785} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sq_AL/sq_AL.dic","https://translator.gres.biz/resources/dictionaries/sq_AL/sq_AL.dic.zip"], "path":"$hunspell$/sq/sq_AL.dic", "date":"2020-03-17T12:21:16+01:00", "size":2605147}
]} ]}
, "Serbian":{"files":[ , "Serbian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sr/sr.aff","https://translator.gres.biz/resources/dictionaries/sr/sr.aff.zip"], "path":"$hunspell$/sr/sr.aff", "date":"2019-04-20T11:24:57+02:00", "size":901060} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sr/sr.aff","https://translator.gres.biz/resources/dictionaries/sr/sr.aff.zip"], "path":"$hunspell$/sr/sr.aff", "date":"2020-03-17T12:21:16+01:00", "size":901060}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sr/sr.dic","https://translator.gres.biz/resources/dictionaries/sr/sr.dic.zip"], "path":"$hunspell$/sr/sr.dic", "date":"2019-04-20T11:24:57+02:00", "size":5878745} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sr/sr.dic","https://translator.gres.biz/resources/dictionaries/sr/sr.dic.zip"], "path":"$hunspell$/sr/sr.dic", "date":"2020-03-17T12:21:16+01:00", "size":5878745}
]} ]}
, "Swedish":{"files":[ , "Swedish":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sv_SE/sv_FI.aff","https://translator.gres.biz/resources/dictionaries/sv_SE/sv_FI.aff.zip"], "path":"$hunspell$/sv/sv_FI.aff", "date":"2015-09-08T21:02:20+00:00", "size":18583} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sv_SE/sv_FI.aff","https://translator.gres.biz/resources/dictionaries/sv_SE/sv_FI.aff.zip"], "path":"$hunspell$/sv/sv_FI.aff", "date":"2020-03-17T12:21:16+01:00", "size":18583}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sv_SE/sv_FI.dic","https://translator.gres.biz/resources/dictionaries/sv_SE/sv_FI.dic.zip"], "path":"$hunspell$/sv/sv_FI.dic", "date":"2016-08-16T20:00:33+00:00", "size":2317112} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sv_SE/sv_FI.dic","https://translator.gres.biz/resources/dictionaries/sv_SE/sv_FI.dic.zip"], "path":"$hunspell$/sv/sv_FI.dic", "date":"2020-03-17T12:21:16+01:00", "size":2317112}
]} ]}
, "Swahili":{"files":[ , "Swahili":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sw_TZ/sw_TZ.aff","https://translator.gres.biz/resources/dictionaries/sw_TZ/sw_TZ.aff.zip"], "path":"$hunspell$/sw/sw_TZ.aff", "date":"2012-10-16T11:09:27-05:00", "size":974} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sw_TZ/sw_TZ.aff","https://translator.gres.biz/resources/dictionaries/sw_TZ/sw_TZ.aff.zip"], "path":"$hunspell$/sw/sw_TZ.aff", "date":"2020-03-17T12:21:16+01:00", "size":974}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sw_TZ/sw_TZ.dic","https://translator.gres.biz/resources/dictionaries/sw_TZ/sw_TZ.dic.zip"], "path":"$hunspell$/sw/sw_TZ.dic", "date":"2012-10-16T11:09:27-05:00", "size":630844} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/sw_TZ/sw_TZ.dic","https://translator.gres.biz/resources/dictionaries/sw_TZ/sw_TZ.dic.zip"], "path":"$hunspell$/sw/sw_TZ.dic", "date":"2020-03-17T12:21:16+01:00", "size":630844}
]} ]}
, "Telugu":{"files":[ , "Telugu":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/te_IN/te_IN.aff","https://translator.gres.biz/resources/dictionaries/te_IN/te_IN.aff.zip"], "path":"$hunspell$/te/te_IN.aff", "date":"2012-10-16T11:09:27-05:00", "size":160} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/te_IN/te_IN.aff","https://translator.gres.biz/resources/dictionaries/te_IN/te_IN.aff.zip"], "path":"$hunspell$/te/te_IN.aff", "date":"2020-03-17T12:21:16+01:00", "size":160}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/te_IN/te_IN.dic","https://translator.gres.biz/resources/dictionaries/te_IN/te_IN.dic.zip"], "path":"$hunspell$/te/te_IN.dic", "date":"2012-10-16T11:09:27-05:00", "size":3402272} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/te_IN/te_IN.dic","https://translator.gres.biz/resources/dictionaries/te_IN/te_IN.dic.zip"], "path":"$hunspell$/te/te_IN.dic", "date":"2020-03-17T12:21:16+01:00", "size":3402272}
]} ]}
, "Thai":{"files":[ , "Thai":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/th_TH/th_TH.aff","https://translator.gres.biz/resources/dictionaries/th_TH/th_TH.aff.zip"], "path":"$hunspell$/th/th_TH.aff", "date":"2019-04-30T09:35:45+02:00", "size":156} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/th_TH/th_TH.aff","https://translator.gres.biz/resources/dictionaries/th_TH/th_TH.aff.zip"], "path":"$hunspell$/th/th_TH.aff", "date":"2020-03-17T12:21:16+01:00", "size":156}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/th_TH/th_TH.dic","https://translator.gres.biz/resources/dictionaries/th_TH/th_TH.dic.zip"], "path":"$hunspell$/th/th_TH.dic", "date":"2019-06-04T14:18:16+02:00", "size":1251425} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/th_TH/th_TH.dic","https://translator.gres.biz/resources/dictionaries/th_TH/th_TH.dic.zip"], "path":"$hunspell$/th/th_TH.dic", "date":"2020-03-17T12:21:16+01:00", "size":1251425}
]} ]}
, "Turkish":{"files":[ , "Turkish":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/tr_TR/tr_TR.aff","https://translator.gres.biz/resources/dictionaries/tr_TR/tr_TR.aff.zip"], "path":"$hunspell$/tr/tr_TR.aff", "date":"2018-08-27T16:55:14+02:00", "size":235315} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/tr_TR/tr_TR.aff","https://translator.gres.biz/resources/dictionaries/tr_TR/tr_TR.aff.zip"], "path":"$hunspell$/tr/tr_TR.aff", "date":"2020-03-17T12:21:16+01:00", "size":235315}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/tr_TR/tr_TR.dic","https://translator.gres.biz/resources/dictionaries/tr_TR/tr_TR.dic.zip"], "path":"$hunspell$/tr/tr_TR.dic", "date":"2018-08-27T16:55:14+02:00", "size":9061155} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/tr_TR/tr_TR.dic","https://translator.gres.biz/resources/dictionaries/tr_TR/tr_TR.dic.zip"], "path":"$hunspell$/tr/tr_TR.dic", "date":"2020-03-17T12:21:16+01:00", "size":9061155}
]} ]}
, "Ukrainian":{"files":[ , "Ukrainian":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/uk_UA/uk_UA.aff","https://translator.gres.biz/resources/dictionaries/uk_UA/uk_UA.aff.zip"], "path":"$hunspell$/uk/uk_UA.aff", "date":"2022-08-28T03:23:22+02:00", "size":203463} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/uk_UA/uk_UA.aff","https://translator.gres.biz/resources/dictionaries/uk_UA/uk_UA.aff.zip"], "path":"$hunspell$/uk/uk_UA.aff", "date":"2020-03-17T12:21:16+01:00", "size":159599}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/uk_UA/uk_UA.dic","https://translator.gres.biz/resources/dictionaries/uk_UA/uk_UA.dic.zip"], "path":"$hunspell$/uk/uk_UA.dic", "date":"2022-08-28T03:23:22+02:00", "size":8355640} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/uk_UA/uk_UA.dic","https://translator.gres.biz/resources/dictionaries/uk_UA/uk_UA.dic.zip"], "path":"$hunspell$/uk/uk_UA.dic", "date":"2020-03-17T12:21:16+01:00", "size":2584267}
]} ]}
, "Vietnamese":{"files":[ , "Vietnamese":{"files":[
{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/vi/vi_VN.aff","https://translator.gres.biz/resources/dictionaries/vi/vi_VN.aff.zip"], "path":"$hunspell$/vi/vi_VN.aff", "date":"2012-10-16T11:09:27-05:00", "size":788} {"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/vi/vi_VN.aff","https://translator.gres.biz/resources/dictionaries/vi/vi_VN.aff.zip"], "path":"$hunspell$/vi/vi_VN.aff", "date":"2020-03-17T12:21:16+01:00", "size":788}
,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/vi/vi_VN.dic","https://translator.gres.biz/resources/dictionaries/vi/vi_VN.dic.zip"], "path":"$hunspell$/vi/vi_VN.dic", "date":"2012-10-16T11:09:27-05:00", "size":39852} ,{"url":["https://cgit.freedesktop.org/libreoffice/dictionaries/plain/vi/vi_VN.dic","https://translator.gres.biz/resources/dictionaries/vi/vi_VN.dic.zip"], "path":"$hunspell$/vi/vi_VN.dic", "date":"2020-03-17T12:21:16+01:00", "size":39852}
]} ]}
} }
@ -588,27 +576,26 @@
,"translators":{ ,"translators":{
"baidu": {"files":[ "baidu": {"files":[
{"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/baidu.js", "path":"$translators$/baidu.js", "md5":"be3eb6d11fa5faebb046c887c9a8f3bd", "size":1501} {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/baidu.js", "path":"$translators$/baidu.js", "md5":"93f7bca9b792877350f54a1d47767583", "size":1306}
]} ]}
,"bing": {"files":[ ,"bing": {"files":[
{"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/bing.js", "path":"$translators$/bing.js", "md5":"a982e9aa6cac598f4c9bf4a56386d13e", "size":1481} {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/bing.js", "path":"$translators$/bing.js", "md5":"5c20fe78c25a4f9e97160fdc3bc4572c", "size":1277}
]} ]}
,"deepl": {"files":[ ,"deepl": {"files":[
{"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/deepl.js", "path":"$translators$/deepl.js", "md5":"76856af9b80c3d0e852ca73f8f1ebbdb", "size":2611} {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/deepl.js", "path":"$translators$/deepl.js", "md5":"a6dfae3f63ca3fa9c7edbfaff87600e4", "size":1596}
]} ]}
,"google": {"files":[ ,"google": {"files":[
{"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google.js", "path":"$translators$/google.js", "md5":"793d6628ac9e26a1f3cc00fa9c863495", "size":1508} {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google.js", "path":"$translators$/google.js", "md5":"16ffead93035e08e8db13279cc8b65a7", "size":1260}
]} ]}
,"google_api": {"files":[ ,"google_api": {"files":[
{"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google_api.js", "path":"$translators$/google_api.js", "md5":"90b9b1a5c8dc52fd4a3f28be93442a56", "size":1030} {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/google_api.js", "path":"$translators$/google_api.js", "md5":"ba0d5fb0e156cccd47e048389c2762fa", "size":865}
]} ]}
,"papago": {"files":[ ,"papago": {"files":[
{"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/papago.js", "path":"$translators$/papago.js", "md5":"603a56fc23990453942064ec53d1eaa3", "size":2164} {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/papago.js", "path":"$translators$/papago.js", "md5":"538bb7280192b18d15d24b07adae2638", "size":1956}
]} ]}
,"yandex": {"files":[ ,"yandex": {"files":[
{"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/yandex.js", "path":"$translators$/yandex.js", "md5":"82c10bddde30f3a1dc6675f7eea71986", "size":1170} {"url":"https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/translators/yandex.js", "path":"$translators$/yandex.js", "md5":"f8c625e8f6ced4a9f5be6791a6ee3f87", "size":957}
]} ]}
} }
} }

15
version.json Normal file
View File

@ -0,0 +1,15 @@
{
"version": 1,
"url": "https://raw.githubusercontent.com/OneMoreGres/ScreenTranslator/master/version.json",
"Application": {
"version": 3,
"compatibleVersion": 3,
"built_in": true,
"versionString": "3.0.0",
"permissions": "0x7755",
"url_win": "disabled",
"path_win": "ScreenTranslator.exe",
"url_linux": "disabled",
"path_linux": "ScreenTranslator"
}
}