Add build and ci scripts

This commit is contained in:
Gres 2020-03-01 12:30:43 +03:00
parent 81f370f5c7
commit f2b7ac1821
15 changed files with 1152 additions and 3 deletions

118
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,118 @@
name: Build
on: [push]
jobs:
release:
name: Create release
if: contains(github.ref, '/tags/')
runs-on: ubuntu-16.04
steps:
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
prerelease: false
- name: Store release url
run: echo "${{ steps.create_release.outputs.upload_url }}" > ./release_upload_url
- name: Upload release url
uses: actions/upload-artifact@v1
with:
path: ./release_upload_url
name: release_upload_url
build:
name: Build ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
env:
OS: ${{ matrix.config.name }}
MSVC_VERSION: 2019/Enterprise
strategy:
matrix:
config:
- { name: "win64", os: windows-latest }
- { name: "win32", os: windows-latest }
- { name: "linux", os: ubuntu-16.04 }
- { name: "macos", os: macos-latest }
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 10
- name: Setup python
uses: actions/setup-python@v1
with:
python-version: "3.x"
- name: Install system libs
if: runner.os == 'Linux'
run: sudo apt install libgl1-mesa-dev libxkbcommon-x11-0
- name: Get Qt
run: python ./share/ci/get_qt.py
- name: Get dependencies
run: |
python ./share/ci/get_leptonica.py
python ./share/ci/get_tesseract.py
- name: Build
run: python ./share/ci/build.py
- name: Create AppImage
if: runner.os == 'Linux'
shell: bash
run: |
python ./share/ci/appimage.py
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
if: env.artifact != ''
uses: actions/upload-artifact@v1
with:
name: ${{ env.artifact }}
path: ./${{ env.artifact }}
- name: Download release url
if: contains(github.ref, '/tags/')
uses: actions/download-artifact@v1
with:
name: release_upload_url
path: ./
- name: Set release env
if: contains(github.ref, '/tags/')
shell: bash
run: echo ::set-env name=upload_url::`cat ./release_upload_url`
- name: Upload release artifacts
if: contains(github.ref, '/tags/')
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ env.upload_url }}
asset_path: ./${{ env.artifact }}
asset_name: ${{ env.artifact }}
asset_content_type: application/zip

View File

@ -87,6 +87,35 @@ OTHER_FILES += \
translators/*.js \
version.json
#TRANSLATIONS += \
# translations/translation_en.ts \
# translations/translation_ru.ts
TRANSLATIONS += \
share/translations/screentranslator_ru.ts
translations.files = $$PWD/share/translations/screentranslator_ru.qm
translators.files = $$PWD/translators/*.js
linux {
PREFIX = /usr
target.path = $$PREFIX/bin
shortcuts.files = $$PWD/share/screentranslator.desktop
shortcuts.path = $$PREFIX/share/applications/
pixmaps.files += $$PWD/share/images/screentranslator.png
pixmaps.path = $$PREFIX/share/icons/hicolor/128x128/apps/
translations.path = $$PREFIX/translations
INSTALLS += target shortcuts pixmaps translations
}
win32 {
RC_ICONS = $$PWD/share/images/icon.ico
translations.path = /translations
target.path = /
translators.path = /translators
INSTALLS += target translations translators
}
mac {
translations.path = Contents/Translations
QMAKE_BUNDLE_DATA += translations
ICON = $$PWD/share/images/icon.icns
}

54
share/ci/appimage.py Normal file
View File

@ -0,0 +1,54 @@
import common as c
from config import *
import os
import sys
import subprocess as sub
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)
exit(0)
artifact_name = '{}-{}.AppImage'.format(app_name, app_version)
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
c.print(artifact_name)
exit(0)
artifact_path = os.path.abspath(artifact_name)
c.print('>> Making appimage')
base_url = 'https://github.com/probonopd/linuxdeployqt/releases/download'
continuous_url = base_url + '/continuous/linuxdeployqt-continuous-x86_64.AppImage'
tagged_url = base_url + '/6/linuxdeployqt-6-x86_64.AppImage'
linuxdeployqt_url = tagged_url
linuxdeployqt_original = os.path.basename(linuxdeployqt_url)
c.download(linuxdeployqt_url, linuxdeployqt_original)
c.run('chmod a+x {}'.format(linuxdeployqt_original))
linuxdeployqt_bin = os.path.abspath('linuxdeployqt')
c.symlink(linuxdeployqt_original, linuxdeployqt_bin)
os.chdir(build_dir)
install_dir = os.path.abspath('appdir')
c.recreate_dir(install_dir)
c.run('make INSTALL_ROOT={0} DESTDIR={0} install'.format(install_dir))
if c.is_inside_docker():
c.run('{} --appimage-extract'.format(linuxdeployqt_bin))
linuxdeployqt_bin = os.path.abspath('squashfs-root/AppRun')
os.environ['LD_LIBRARY_PATH'] = dependencies_dir + '/lib'
os.environ['VERSION'] = app_version
# debug flags: -unsupported-bundle-everything -unsupported-allow-new-glibc
flags = '' if os.getenv("DEBUG") is None else '-unsupported-allow-new-glibc'
# remove serial post dependency
os.rename(qt_dir + '/plugins/position', qt_dir + '/plugins/position1')
c.run('{} {}/usr/share/applications/*.desktop {} -appimage -qmake={}/bin/qmake'.format(
linuxdeployqt_bin, install_dir, flags, qt_dir))
os.rename(qt_dir + '/plugins/position1', qt_dir + '/plugins/position')
c.run('mv {}-{}*.AppImage "{}"'.format(app_name, app_version, artifact_path))

24
share/ci/build.py Normal file
View File

@ -0,0 +1,24 @@
import common as c
from config import *
import os
import platform
c.print('>> Building {} on {}'.format(app_name, os_name))
c.add_to_path(os.path.abspath(qt_dir + '/bin'))
os.environ['ST_DEPS_DIR'] = dependencies_dir
if platform.system() == "Windows":
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
c.apply_cmd_env(env_cmd)
c.recreate_dir(build_dir)
os.chdir(build_dir)
c.run('lupdate "{}"'.format(pro_file))
c.run('lrelease "{}"'.format(pro_file))
c.set_make_threaded()
c.run('qmake "{}"'.format(pro_file))
make_cmd = c.get_make_cmd()
c.run(make_cmd)

213
share/ci/common.py Normal file
View File

@ -0,0 +1,213 @@
import os
import subprocess as sub
import urllib.request
from shutil import which
import zipfile
import tarfile
import functools
import shutil
import multiprocessing
import platform
import re
print = functools.partial(print, flush=True)
def run(cmd, capture_output=False, silent=False):
print('>> Running', cmd)
if capture_output:
result = sub.run(cmd, check=True, shell=True, universal_newlines=True,
stdout=sub.PIPE, stderr=sub.STDOUT)
if not silent:
print(result.stdout)
else:
if not silent:
result = sub.run(cmd, check=True, shell=True)
else:
result = sub.run(cmd, check=True, shell=True,
stdout=sub.DEVNULL, stderr=sub.DEVNULL)
return result
def download(url, out, force=False):
print('>> Downloading', url, 'as', out)
if not force and os.path.exists(out):
print('>>', out, 'already exists')
return
out_path = os.path.dirname(out)
if len(out_path) > 0:
os.makedirs(out_path, exist_ok=True)
urllib.request.urlretrieve(url, out)
def extract(src, dest):
abs_path = os.path.abspath(src)
print('>> Extracting', abs_path, 'to', dest)
if len(dest) > 0:
os.makedirs(dest, exist_ok=True)
if which('cmake'):
out = run('cmake -E tar t "{}"'.format(abs_path),
capture_output=True, silent=True)
files = out.stdout.split('\n')
already_exist = True
for file in files:
if not os.path.exists(os.path.join(dest, file)):
already_exist = False
break
if already_exist:
print('>> All files already exist')
return
sub.run('cmake -E tar xvf "{}"'.format(abs_path),
check=True, shell=True, cwd=dest)
return
is_tar_smth = src.endswith('.tar', 0, src.rfind('.'))
if which('7z'):
sub.run('7z x "{}" -o"{}"'.format(abs_path, dest),
check=True, shell=True, input=b'S\n')
if is_tar_smth:
inner_name = abs_path[:abs_path.rfind('.')]
sub.run('7z x "{}" -o"{}"'.format(inner_name, dest),
check=True, shell=True, input=b'S\n')
return
if src.endswith('.tar') or is_tar_smth:
path = abs_path if platform.system() != "Windows" else os.path.relpath(abs_path)
if which('tar'):
sub.run('tar xf "{}" --keep-newer-files -C "{}"'.format(path, dest),
check=True, shell=True)
return
raise RuntimeError('No archiver to extract {} file'.format(src))
def get_folder_files(path):
result = []
for root, _, files in os.walk(path):
for file in files:
result.append(os.path.join(root, file))
return result
def get_archive_top_dir(path):
"""Return first top level folder name in given archive or raises RuntimeError"""
with tarfile.open(path) as tar:
first = tar.next()
if not first is None:
result = os.path.dirname(first.path)
if len(result) == 0:
result = first.path
return result
raise RuntimeError('Failed to open file or empty archive ' + path)
def archive(files, out):
print('>> Archiving', files, 'into', out)
if out.endswith('.zip'):
arc = zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED)
for f in files:
arc.write(f)
arc.close()
return
if out.endswith('.tar.gz'):
arc = tarfile.open(out, 'w|gz')
for f in files:
arc.add(f)
arc.close()
return
raise RuntimeError('No archiver to create {} file'.format(out))
def symlink(src, dest):
print('>> Creating symlink', src, '=>', dest)
norm_src = os.path.normcase(src)
norm_dest = os.path.normcase(dest)
if os.path.lexists(norm_dest):
os.remove(norm_dest)
os.symlink(norm_src, norm_dest,
target_is_directory=os.path.isdir(norm_src))
def recreate_dir(path):
shutil.rmtree(path, ignore_errors=True)
os.mkdir(path)
def add_to_path(entry, prepend=True):
path_separator = ';' if platform.system() == "Windows" else ':'
os.environ['PATH'] = entry + path_separator + os.environ['PATH']
def get_msvc_env_cmd(bitness='64', msvc_version=''):
"""Return environment setup command for running msvc compiler for current platform"""
if platform.system() != "Windows":
return None
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 + '"'
def get_cmake_arch_args(bitness='64'):
if platform.system() != "Windows":
return ''
return '-A {}'.format('Win32' if bitness == '32' else 'x64')
def get_make_cmd():
"""Return `make` command for current platform"""
return 'nmake' if platform.system() == "Windows" else 'make'
def set_make_threaded():
"""Adjust environment to run threaded make command"""
if platform.system() == "Windows":
os.environ['CL'] = '/MP'
else:
os.environ['MAKEFLAGS'] = '-j{}'.format(multiprocessing.cpu_count())
def is_inside_docker():
""" Return True if running in a Docker container """
with open('/proc/1/cgroup', 'rt') as f:
return 'docker' in f.read()
def ensure_got_path(path):
os.makedirs(path, exist_ok=True)
def apply_cmd_env(cmd):
"""Run cmd and apply its modified environment"""
print('>> Applying env after', cmd)
env_cmd = 'env' if which('env') else 'set'
env = sub.run('{} && {}'.format(cmd, env_cmd), shell=True, universal_newlines=True,
stdout=sub.PIPE)
is_mingw = 'MINGW_CHOST' in os.environ
lines = env.stdout.split('\n')
for line in lines:
match = re.match(r"^([a-zA-Z0-9_-]+)=(.*)$", line)
if not match:
continue
key, value = match.groups()
if key in os.environ and os.environ[key] == value:
continue
if is_mingw and key.find('PATH') != -1 and value.find('/') != -1:
value = value.replace(':', ';')
value = re.sub(r'/(\w)/', r'\1:\\', value)
value = value.replace('/', '\\')
os.environ[key] = value

23
share/ci/config.py Normal file
View File

@ -0,0 +1,23 @@
from os import getenv, path
app_name = 'ScreenTranslator'
app_version = '3.0.0'
target_name = app_name
qt_version = '5.14.0'
qt_modules = ['qtbase', 'qttools', 'icu',
'qttranslations', 'qtx11extras', 'qtwebengine', 'qtwebchannel',
'qtdeclarative', 'qtlocation', 'opengl32sw', 'd3dcompiler_47']
qt_dir = path.abspath('qt')
build_dir = path.abspath('build')
dependencies_dir = path.abspath('deps')
pro_file = path.abspath(path.dirname(__file__) +
'/../../screen-translator.pro')
ts_files_dir = path.abspath(path.dirname(__file__) + '/../../translations')
os_name = getenv('OS', 'linux')
app_version += {'linux': '', 'macos': '-experimental',
'win32': '', 'win64': ''}[os_name]
bitness = '32' if os_name == 'win32' else '64'
msvc_version = getenv('MSVC_VERSION', '2017/Community')

71
share/ci/get_leptonica.py Normal file
View File

@ -0,0 +1,71 @@
import common as c
from config import bitness, msvc_version, build_dir, dependencies_dir
import os
import platform
c.print('>> Installing leptonica')
install_dir = dependencies_dir
url = 'http://www.leptonica.org/source/leptonica-1.78.0.tar.gz'
required_version = '1.78.0'
def check_existing():
if platform.system() == "Windows":
dll = install_dir + '/bin/leptonica-1.78.0.dll'
lib = install_dir + '/lib/leptonica-1.78.0.lib'
if not os.path.exists(dll) or not os.path.exists(lib):
return False
c.symlink(dll, install_dir + '/bin/leptonica.dll')
c.symlink(lib, install_dir + '/lib/leptonica.lib')
elif platform.system() == "Darwin":
lib = install_dir + '/lib/libleptonica.1.78.0.dylib'
if not os.path.exists(lib):
return False
c.symlink(lib, install_dir + '/lib/libleptonica.dylib')
else:
if not os.path.exists(install_dir + '/lib/libleptonica.so'):
return False
includes_path = install_dir + '/include/leptonica'
if len(c.get_folder_files(includes_path)) == 0:
return False
version_file = install_dir + '/cmake/LeptonicaConfig-version.cmake'
if not os.path.exists(version_file):
return False
with open(version_file, 'rt') as f:
existing_version = f.readline()[22:28] # set(Leptonica_VERSION 1.78.0)
if existing_version != required_version:
return False
return True
if check_existing():
c.print('>> Using cached')
exit(0)
archive = os.path.basename(url)
c.download(url, archive)
src_dir = os.path.abspath('leptonica_src')
c.extract(archive, '.')
c.symlink(c.get_archive_top_dir(archive), src_dir)
c.ensure_got_path(install_dir)
c.recreate_dir(build_dir)
os.chdir(build_dir)
cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}"'.format(src_dir, install_dir)
if platform.system() == "Windows":
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
c.apply_cmd_env(env_cmd)
cmake_args += ' ' + c.get_cmake_arch_args(bitness=bitness)
c.set_make_threaded()
c.run('cmake {}'.format(cmake_args))
c.run('cmake --build . --config Release')
c.run('cmake --build . --target install --config Release')

82
share/ci/get_qt.py Normal file
View File

@ -0,0 +1,82 @@
import common as c
from config import qt_modules, qt_version, qt_dir, os_name
import sys
import xml.etree.ElementTree as ET
c.print('>> Downloading Qt {} ({}) for {}'.format(
qt_version, qt_modules, os_name))
if os_name == 'linux':
os_url = 'linux_x64'
kit_arch = 'gcc_64'
qt_dir_prefix = '{}/gcc_64'.format(qt_version)
elif os_name == 'win32':
os_url = 'windows_x86'
kit_arch = 'win32_msvc2017'
qt_dir_prefix = '{}/msvc2017'.format(qt_version)
elif os_name == 'win64':
os_url = 'windows_x86'
kit_arch = 'win64_msvc2017_64'
qt_dir_prefix = '{}/msvc2017_64'.format(qt_version)
elif os_name == 'macos':
os_url = 'mac_x64'
kit_arch = 'clang_64'
qt_dir_prefix = '{}/clang_64'.format(qt_version)
qt_version_dotless = qt_version.replace('.', '')
base_url = 'https://download.qt.io/online/qtsdkrepository/{}/desktop/qt5_{}' \
.format(os_url, qt_version_dotless)
updates_file = 'Updates-{}-{}.xml'.format(qt_version, os_name)
c.download(base_url + '/Updates.xml', updates_file)
updates = ET.parse(updates_file)
updates_root = updates.getroot()
all_modules = {}
for i in updates_root.iter('PackageUpdate'):
name = i.find('Name').text
if 'debug' in name or not kit_arch in name:
continue
archives = i.find('DownloadableArchives')
if archives.text is None:
continue
archives_parts = archives.text.split(',')
version = i.find('Version').text
for archive in archives_parts:
archive = archive.strip()
parts = archive.split('-')
module_name = parts[0]
all_modules[module_name] = {'package': name, 'file': version + archive}
if len(sys.argv) > 1: # handle subcommand
if sys.argv[1] == 'list':
c.print('Available modules:')
for k in iter(sorted(all_modules.keys())):
c.print(k, '---', all_modules[k]['file'])
exit(0)
for module in qt_modules:
if module not in all_modules:
c.print('>> Required module {} not available'.format(module))
continue
file_name = all_modules[module]['file']
package = all_modules[module]['package']
c.download(base_url + '/' + package + '/' + file_name, file_name)
c.extract(file_name, '.')
c.symlink(qt_dir_prefix, qt_dir)
c.print('>> Updating license')
config_name = qt_dir + '/mkspecs/qconfig.pri'
config = ''
with open(config_name, 'r') as f:
config = f.read()
config = config.replace('Enterprise', 'OpenSource')
config = config.replace('licheck.exe', '')
config = config.replace('licheck64', '')
config = config.replace('licheck_mac', '')
with open(config_name, 'w') as f:
f.write(config)

74
share/ci/get_tesseract.py Normal file
View File

@ -0,0 +1,74 @@
import common as c
from config import bitness, msvc_version, build_dir, dependencies_dir
import os
import platform
c.print('>> Installing tesseract')
install_dir = dependencies_dir
url = 'https://github.com/tesseract-ocr/tesseract/archive/4.1.1.tar.gz'
required_version = '4.1.1'
def check_existing():
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
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.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
includes_path = install_dir + '/include/tesseract'
if len(c.get_folder_files(includes_path)) == 0:
return False
version_file = install_dir + '/cmake/TesseractConfig-version.cmake'
if not os.path.exists(version_file):
return False
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
if check_existing():
c.print('>> Using cached')
exit(0)
archive = 'tesseract-' + os.path.basename(url)
c.download(url, archive)
src_dir = os.path.abspath('tesseract_src')
c.extract(archive, '.')
c.symlink(c.get_archive_top_dir(archive), src_dir)
c.ensure_got_path(install_dir)
c.recreate_dir(build_dir)
os.chdir(build_dir)
os.environ['PKG_CONFIG_PATH'] = install_dir + '/lib/pkgconfig'
cmake_args = '"{}" -DCMAKE_INSTALL_PREFIX="{}" -DBUILD_TRAINING_TOOLS=OFF \
-DBUILD_TESTS=OFF'.format(src_dir, install_dir)
if platform.system() == "Windows":
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
c.apply_cmd_env(env_cmd)
cmake_args += ' ' + c.get_cmake_arch_args(bitness=bitness)
c.set_make_threaded()
c.run('cmake {}'.format(cmake_args))
c.run('cmake --build . --config Release')
c.run('cmake --build . --target install --config Release')

18
share/ci/macdeploy.py Normal file
View File

@ -0,0 +1,18 @@
import common as c
from config import *
import os
import sys
artifact_name = '{}-{}.dmg'.format(app_name, app_version)
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
c.print(artifact_name)
exit(0)
artifact_path = os.path.abspath(artifact_name)
c.print('>> Making mac deploy')
os.chdir(build_dir)
build_target = build_dir + '/' + target_name + '.app'
built_dmg = build_dir + '/' + target_name + '.dmg'
c.run('{}/bin/macdeployqt "{}" -dmg'.format(qt_dir, build_target))
os.rename(built_dmg, artifact_path)

35
share/ci/windeploy.py Normal file
View File

@ -0,0 +1,35 @@
import common as c
from config import *
import os
import sys
import shutil
artifact_name = '{}-{}-{}.zip'.format(app_name, app_version, os_name)
if len(sys.argv) > 1 and sys.argv[1] == 'artifact_name': # subcommand
c.print(artifact_name)
exit(0)
artifact_path = os.path.abspath(artifact_name)
c.print('>> Making win deploy')
if os_name.startswith('win'):
env_cmd = c.get_msvc_env_cmd(bitness=bitness, msvc_version=msvc_version)
c.apply_cmd_env(env_cmd)
pwd = os.getcwd()
os.chdir(build_dir)
install_dir = os.path.abspath(app_name)
c.recreate_dir(install_dir)
c.run('nmake INSTALL_ROOT="{0}" DESTDIR="{0}" install'.format(install_dir))
c.run('{}/bin/windeployqt.exe "{}"'.format(qt_dir, install_dir))
libs_dir = os.path.join(dependencies_dir, 'bin')
for file in os.scandir(libs_dir):
if file.is_file(follow_symlinks=False) and file.name.endswith('.dll'):
full_name = os.path.join(libs_dir, file.name)
c.print('>> Copying {} to {}'.format(full_name, install_dir))
shutil.copy(full_name, install_dir)
c.archive(c.get_folder_files(os.path.relpath(install_dir)), artifact_path)

BIN
share/images/icon.icns Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,9 @@
[Desktop Entry]
Comment=OCR and translation tool
Exec=screen-translator
GenericName=Screen Translator
Name=ScreenTranslator
Icon=screentranslator
Terminal=false
Type=Application
Categories=Utility;Core;Qt;

View File

@ -0,0 +1,399 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ru_RU">
<context>
<name>QObject</name>
<message>
<location filename="../../src/main.cpp" line="31"/>
<source>OCR and translation tool</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="189"/>
<source>The last result was copied to the clipboard.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="195"/>
<source>Optical character recognition (OCR) and translation tool
Author: Gres (translator@gres.biz)
Version: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/manager.cpp" line="200"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/ocr/tesseract.cpp" line="173"/>
<source>unknown recognition language: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/ocr/tesseract.cpp" line="185"/>
<source>troubles with tessdata</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/ocr/tesseract.cpp" line="213"/>
<source>Failed to recognize text</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/service/singleapplication.cpp" line="23"/>
<source>Another instance is running. Lock file is busy.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/translate/webpage.cpp" line="96"/>
<source>unknown translation language: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>RecognizeWorker</name>
<message>
<location filename="../../src/ocr/recognizerworker.cpp" line="21"/>
<source>Failed to init OCR engine: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Recognizer</name>
<message>
<location filename="../../src/ocr/recognizer.cpp" line="43"/>
<source>Tessdata path is empty</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsEditor</name>
<message>
<location filename="../../src/settingseditor.ui" line="14"/>
<source>Настройки</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="53"/>
<source>Shortcuts</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="59"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Сочетание клавиш для перехода в режим захвата.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="62"/>
<source>Capture</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="72"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Сочетание клавиш для перехода в режим захвата, но с использованием последнего использованного, а не текущего, изображения.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="75"/>
<source>Repeat capture</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="85"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Сочетание клавиш для повторного отображения последнего результата.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="88"/>
<source>Show last result</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="98"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Сочетание клавиш для копирования последнего результата в буфер обмена.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="101"/>
<source>Copy result to clipboard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="114"/>
<source>Proxy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="120"/>
<source>Type:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="130"/>
<source>User:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="140"/>
<source>Address:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="150"/>
<source>Password:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="160"/>
<source>Port:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="177"/>
<source>save password (unsafe)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="204"/>
<location filename="../../src/settingseditor.ui" line="261"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Заполняется на основании содержания tessdata&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="207"/>
<source>Language</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="217"/>
<source>...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="237"/>
<location filename="../../src/settingseditor.cpp" line="133"/>
<source>Path to tessdata</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="247"/>
<source>Enter path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="264"/>
<location filename="../../src/settingseditor.ui" line="341"/>
<source>Language:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="277"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Символы, регулярно распознаваемые с ошибками. При обнаружении будут заменены на указанные.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="280"/>
<source>Corrections</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="304"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Отображает окно переводчика. Следует использовать только для разработки переводчиков.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="307"/>
<source>Debug mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="314"/>
<source> secs</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="321"/>
<source>Ignore SSL errors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="328"/>
<source>Translators</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="338"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Язык, на который осуществляется перевод.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="351"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Отображены в порядке убывания приоритета.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="367"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Необходимо ли переводить (вкл) распознанный текст.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="370"/>
<source>Translate text</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="380"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Максимальное время, которое может быть затрачено на перевод, чтобы он не считался &amp;quot;зависшим&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="383"/>
<source>Single translator timeout:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="400"/>
<source>Result type</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="412"/>
<source>Tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="425"/>
<source>Window</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="471"/>
<source>Check for updates:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.ui" line="487"/>
<source>Check now</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="21"/>
<source>General</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="21"/>
<source>Recognition</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="21"/>
<source>Correction</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="22"/>
<source>Translation</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="22"/>
<source>Representation</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="23"/>
<source>Update</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="65"/>
<source>Never</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="65"/>
<source>Daily</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="65"/>
<source>Weekly</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/settingseditor.cpp" line="65"/>
<source>Monthly</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Translator</name>
<message>
<location filename="../../src/translate/translator.cpp" line="101"/>
<source>No translators loaded from %1 (named %2)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/translate/translator.cpp" line="168"/>
<source>All translators failed</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>TrayIcon</name>
<message>
<location filename="../../src/trayicon.cpp" line="45"/>
<source>Failed to register global shortcuts:
%1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="140"/>
<location filename="../../src/trayicon.cpp" line="147"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="178"/>
<source>Capture</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="183"/>
<source>Repeat capture</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="189"/>
<source>Result</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="191"/>
<source>Show</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="196"/>
<source>To clipboard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="203"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="209"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../src/trayicon.cpp" line="215"/>
<source>Quit</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>