diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a5d77ab..a90a5c77 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## [0.7.3]
+### Improvements
+- Not breaking the application when a i18n (translation) key was not found
+
## [0.7.2] 2019-11-01
### Improvements
- Snap
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 178c7035..83c87a6a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,3 +20,11 @@ The way to go here is to ask yourself if the improvement would be useful for mor
* Explain why you think these changes could be useful
* If it fixes a bug, be sure to link to the issue itself.
* Follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) code style to keep the code consistent.
+
+# Adding a new translation
+* To add a new translation, you will have to create a file in each directory listed below named as the first two letters in the ISO format (e.g: for 'english' would be 'en'):
+- **bauh/view/resources/locale**
+- **bauh/gems/appimage/resources/locale**
+- **bauh/gems/arch/resources/locale**
+- **bauh/gems/flatpak/resources/locale**
+- **bauh/gems/snap/resources/locale**
diff --git a/README.md b/README.md
index b85589e5..84a97bd1 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@ It has a **tray mode** (see **Settings** below) that attaches the application ic
This project has an official Twitter account ( **@bauh4linux** ) so people can stay on top of its news.
+To contribute with this project, have a look at [CONTRIBUTING.md](https://github.com/vinifmor/bauh/blob/master/CONTRIBUTING.md)
+

@@ -65,13 +67,13 @@ If you do not want to clone / download this repository, go to your **Home** fold
In order to autostart the application, use your Desktop Environment settings to register it as a startup application / script (**bauh --tray=1**).
### Gems ( package technology support )
-#### Flatpak ( flatpak gem )
+#### Flatpak ( flatpak )
- The user is able to search, install, uninstall, downgrade, launch and retrieve the applications history
-#### Snap ( snap gem )
+#### Snap ( snap )
- The user is able to search, install, uninstall, refresh, launch and downgrade applications
-#### AUR ( arch gem )
+#### AUR ( arch )
- It is **not enabled by default**
- The user is able to search, install, uninstall, downgrade, launch and retrieve the packages history
- It handles conflicts, and missing / optional packages installations ( including from your distro mirrors )
@@ -90,7 +92,7 @@ will be pre-downloaded faster ( it does **NOT** modify your **pacman** settings
- Arch package memory-indexer running every 20 minutes. This memory index is used when AUR Api cannot handle the amount of results found for a given search. It can be disabled via the environment variable **BAUH_ARCH_AUR_INDEX_UPDATER=0**.
- If some of your installed packages are not categorized, send an e-mail to **bauh4linux@gmail.com** informing their names and categories in the following format: ```name=category1[,category2,category3,...]```
-#### AppImage ( appimage gem )
+#### AppImage ( appimage )
- The user is able to search, install, uninstall, downgrade, launch and retrieve the applications history
- Supported sources: [AppImageHub](https://appimage.github.io) ( **applications with no releases published to GitHub are currently not available** )
- Faster downloads if **aria2c** is installed. Same behavior described in the **AUR support** section.
diff --git a/bauh/__init__.py b/bauh/__init__.py
index 85695271..932ff985 100644
--- a/bauh/__init__.py
+++ b/bauh/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '0.7.2'
+__version__ = '0.7.3'
__app_name__ = 'bauh'
import os
diff --git a/bauh/api/abstract/context.py b/bauh/api/abstract/context.py
index 056998f5..0cd3d6dc 100644
--- a/bauh/api/abstract/context.py
+++ b/bauh/api/abstract/context.py
@@ -1,16 +1,16 @@
import logging
-import platform
import sys
from bauh.api.abstract.cache import MemoryCacheFactory
from bauh.api.abstract.disk import DiskCacheLoaderFactory
from bauh.api.abstract.download import FileDownloader
from bauh.api.http import HttpClient
+from bauh.view.util.translation import I18n
class ApplicationContext:
- def __init__(self, disk_cache: bool, download_icons: bool, http_client: HttpClient, app_root_dir: str, i18n: dict,
+ def __init__(self, disk_cache: bool, download_icons: bool, http_client: HttpClient, app_root_dir: str, i18n: I18n,
cache_factory: MemoryCacheFactory, disk_loader_factory: DiskCacheLoaderFactory,
logger: logging.Logger, file_downloader: FileDownloader, distro: str):
"""
@@ -18,7 +18,7 @@ class ApplicationContext:
:param download_icons: if packages icons should be downloaded
:param http_client: a shared instance of http client
:param app_root_dir: GUI root dir
- :param i18n: the i18n dictionary keys
+ :param i18n: the translation keys
:param cache_factory:
:param disk_loader_factory:
:param logger: a logger instance
diff --git a/bauh/app.py b/bauh/app.py
index 4a332791..89140feb 100755
--- a/bauh/app.py
+++ b/bauh/app.py
@@ -12,9 +12,12 @@ from bauh.view.core.controller import GenericSoftwareManager
from bauh.view.core.downloader import AdaptableFileDownloader
from bauh.view.qt.systray import TrayIcon
from bauh.view.qt.window import ManageWindow
-from bauh.view.util import util, logs, resource
+from bauh.view.util import util, logs, resource, translation
from bauh.view.util.cache import DefaultMemoryCacheFactory, CacheCleaner
from bauh.view.util.disk import DefaultDiskCacheLoaderFactory
+from bauh.view.util.translation import I18n
+
+DEFAULT_I18N_KEY = 'en'
def main():
@@ -25,7 +28,9 @@ def main():
logger = logs.new_logger(__app_name__, bool(args.logs))
app_args.validate(args, logger)
- i18n_key, i18n = util.get_locale_keys(args.locale)
+ i18n_key, current_i18n = translation.get_locale_keys(args.locale)
+ default_i18n = translation.get_locale_keys(DEFAULT_I18N_KEY)[1] if i18n_key != DEFAULT_I18N_KEY else {}
+ i18n = I18n(current_i18n, default_i18n)
cache_cleaner = CacheCleaner()
cache_factory = DefaultMemoryCacheFactory(expiration_time=args.cache_exp, cleaner=cache_cleaner)
@@ -57,7 +62,7 @@ def main():
if app.style().objectName().lower() not in {'fusion', 'breeze'}:
app.setStyle('Fusion')
- managers = gems.load_managers(context=context, locale=i18n_key, config=user_config)
+ managers = gems.load_managers(context=context, locale=i18n_key, config=user_config, default_locale=DEFAULT_I18N_KEY)
manager = GenericSoftwareManager(managers, context=context, app_args=args)
manager.prepare()
diff --git a/bauh/gems/arch/confirmation.py b/bauh/gems/arch/confirmation.py
index d47765a3..dd114388 100644
--- a/bauh/gems/arch/confirmation.py
+++ b/bauh/gems/arch/confirmation.py
@@ -2,11 +2,11 @@ from typing import Set
from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.view import MultipleSelectComponent, InputOption
-
from bauh.commons.html import bold
+from bauh.view.util.translation import I18n
-def request_optional_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatcher, i18n: dict) -> Set[str]:
+def request_optional_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatcher, i18n: I18n) -> Set[str]:
opts = [InputOption('{}{} ( {} )'.format(p, ': ' + d['desc'] if d['desc'] else '', d['mirror'].upper()), p) for p, d in pkg_mirrors.items()]
view_opts = MultipleSelectComponent(label='',
options=opts,
@@ -21,7 +21,7 @@ def request_optional_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatch
return {o.value for o in view_opts.values}
-def request_install_missing_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatcher, i18n: dict) -> bool:
+def request_install_missing_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatcher, i18n: I18n) -> bool:
deps_str = ''.join(['
- {} ( {} )'.format(d, m.upper()) for d, m in pkg_mirrors.items()])
msg = '
{}
'.format(i18n['arch.missing_deps.body'].format(bold(pkgname)) + ':
' + deps_str)
msg += i18n['ask.continue']
diff --git a/bauh/gems/arch/message.py b/bauh/gems/arch/message.py
index df02103b..320cf23b 100644
--- a/bauh/gems/arch/message.py
+++ b/bauh/gems/arch/message.py
@@ -1,21 +1,22 @@
from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.view import MessageType
from bauh.commons.html import bold
+from bauh.view.util.translation import I18n
-def show_dep_not_installed(watcher: ProcessWatcher, pkgname: str, depname: str, i18n: dict):
+def show_dep_not_installed(watcher: ProcessWatcher, pkgname: str, depname: str, i18n: I18n):
watcher.show_message(title=i18n['error'],
body=i18n['arch.install.dependency.install.error'].format(bold(depname), bold(pkgname)),
type_=MessageType.ERROR)
-def show_dep_not_found(depname: str, i18n: dict, watcher: ProcessWatcher):
+def show_dep_not_found(depname: str, i18n: I18n, watcher: ProcessWatcher):
watcher.show_message(title=i18n['arch.install.dep_not_found.title'],
body=i18n['arch.install.dep_not_found.body'].format(bold(depname)),
type_=MessageType.ERROR)
-def show_optdep_not_installed(depname: str, watcher: ProcessWatcher, i18n: dict):
+def show_optdep_not_installed(depname: str, watcher: ProcessWatcher, i18n: I18n):
watcher.show_message(title=i18n['error'],
body=i18n['arch.install.optdep.error'].format(bold(depname)),
type_=MessageType.ERROR)
diff --git a/bauh/view/core/downloader.py b/bauh/view/core/downloader.py
index 960fc3ab..d29231fb 100644
--- a/bauh/view/core/downloader.py
+++ b/bauh/view/core/downloader.py
@@ -8,11 +8,12 @@ from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.http import HttpClient
from bauh.commons.html import bold
from bauh.commons.system import run_cmd, new_subprocess, ProcessHandler, SystemProcess, SimpleProcess
+from bauh.view.util.translation import I18n
class AdaptableFileDownloader(FileDownloader):
- def __init__(self, logger: logging.Logger, multithread_enabled: bool, i18n: dict, http_client: HttpClient):
+ def __init__(self, logger: logging.Logger, multithread_enabled: bool, i18n: I18n, http_client: HttpClient):
self.logger = logger
self.multithread_enabled = multithread_enabled
self.i18n = i18n
diff --git a/bauh/view/core/gems.py b/bauh/view/core/gems.py
index bfd140d4..c9756759 100644
--- a/bauh/view/core/gems.py
+++ b/bauh/view/core/gems.py
@@ -6,7 +6,7 @@ from typing import List
from bauh import ROOT_DIR
from bauh.api.abstract.controller import SoftwareManager, ApplicationContext
from bauh.view.core.config import Configuration
-from bauh.view.util import util
+from bauh.view.util import util, translation
def find_manager(member):
@@ -20,7 +20,7 @@ def find_manager(member):
return manager_found
-def load_managers(locale: str, context: ApplicationContext, config: Configuration) -> List[SoftwareManager]:
+def load_managers(locale: str, context: ApplicationContext, config: Configuration, default_locale: str) -> List[SoftwareManager]:
managers = []
for f in os.scandir(ROOT_DIR + '/gems'):
@@ -37,7 +37,10 @@ def load_managers(locale: str, context: ApplicationContext, config: Configuratio
locale_path = '{}/resources/locale'.format(f.path)
if os.path.exists(locale_path):
- context.i18n.update(util.get_locale_keys(locale, locale_path)[1])
+ context.i18n.current.update(translation.get_locale_keys(locale, locale_path)[1])
+
+ if default_locale and context.i18n.default:
+ context.i18n.default.update(translation.get_locale_keys(default_locale, locale_path)[1])
man = manager_class(context=context)
diff --git a/bauh/view/qt/about.py b/bauh/view/qt/about.py
index 7f3971a3..39065f0f 100644
--- a/bauh/view/qt/about.py
+++ b/bauh/view/qt/about.py
@@ -1,11 +1,12 @@
from glob import glob
from PyQt5.QtCore import Qt
-from PyQt5.QtGui import QPixmap, QIcon
+from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QVBoxLayout, QDialog, QLabel, QWidget, QHBoxLayout
from bauh import __version__, __app_name__, ROOT_DIR
from bauh.view.util import resource
+from bauh.view.util.translation import I18n
PROJECT_URL = 'https://github.com/vinifmor/' + __app_name__
LICENSE_URL = 'https://raw.githubusercontent.com/vinifmor/{}/master/LICENSE'.format(__app_name__)
@@ -13,9 +14,9 @@ LICENSE_URL = 'https://raw.githubusercontent.com/vinifmor/{}/master/LICENSE'.for
class AboutDialog(QDialog):
- def __init__(self, locale_keys: dict):
+ def __init__(self, i18n: I18n):
super(AboutDialog, self).__init__()
- self.setWindowTitle(locale_keys['tray.action.about'])
+ self.setWindowTitle(i18n['tray.action.about'])
layout = QVBoxLayout()
self.setLayout(layout)
@@ -33,7 +34,7 @@ class AboutDialog(QDialog):
line_desc = QLabel(self)
line_desc.setStyleSheet('font-size: 12px; font-weight: bold;')
- line_desc.setText(locale_keys['about.info.desc'])
+ line_desc.setText(i18n['about.info.desc'])
line_desc.setAlignment(Qt.AlignCenter)
line_desc.setMinimumWidth(400)
layout.addWidget(line_desc)
@@ -55,21 +56,21 @@ class AboutDialog(QDialog):
layout.addWidget(gems_widget)
layout.addWidget(QLabel(''))
- label_version = QLabel(locale_keys['version'].lower() + ' ' + __version__)
+ label_version = QLabel(i18n['version'].lower() + ' ' + __version__)
label_version.setStyleSheet('QLabel { font-size: 11px; font-weight: bold }')
label_version.setAlignment(Qt.AlignCenter)
layout.addWidget(label_version)
label_more_info = QLabel()
label_more_info.setStyleSheet('font-size: 11px;')
- label_more_info.setText(locale_keys['about.info.link'] + ": {url}".format(url=PROJECT_URL))
+ label_more_info.setText(i18n['about.info.link'] + ": {url}".format(url=PROJECT_URL))
label_more_info.setOpenExternalLinks(True)
label_more_info.setAlignment(Qt.AlignCenter)
layout.addWidget(label_more_info)
label_license = QLabel()
label_license.setStyleSheet('font-size: 11px;')
- label_license.setText("{}".format(LICENSE_URL, locale_keys['about.info.license']))
+ label_license.setText("{}".format(LICENSE_URL, i18n['about.info.license']))
label_license.setOpenExternalLinks(True)
label_license.setAlignment(Qt.AlignCenter)
layout.addWidget(label_license)
@@ -78,7 +79,7 @@ class AboutDialog(QDialog):
label_rate = QLabel()
label_rate.setStyleSheet('font-size: 11px; font-weight: bold;')
- label_rate.setText(locale_keys['about.info.rate'] + ' :)')
+ label_rate.setText(i18n['about.info.rate'] + ' :)')
label_rate.setOpenExternalLinks(True)
label_rate.setAlignment(Qt.AlignCenter)
layout.addWidget(label_rate)
diff --git a/bauh/view/qt/apps_table.py b/bauh/view/qt/apps_table.py
index c9ea76c4..040fab21 100644
--- a/bauh/view/qt/apps_table.py
+++ b/bauh/view/qt/apps_table.py
@@ -15,6 +15,7 @@ from bauh.view.qt import dialog
from bauh.view.qt.components import IconButton
from bauh.view.qt.view_model import PackageView, PackageViewStatus
from bauh.view.util import resource
+from bauh.view.util.translation import I18n
INSTALL_BT_STYLE = 'background: {back}; color: white; font-size: 10px; font-weight: bold'
@@ -25,7 +26,7 @@ PUBLISHER_MAX_SIZE = 25
class UpdateToggleButton(QWidget):
- def __init__(self, app_view: PackageView, root: QWidget, i18n: dict, checked: bool = True, clickable: bool = True):
+ def __init__(self, app_view: PackageView, root: QWidget, i18n: I18n, checked: bool = True, clickable: bool = True):
super(UpdateToggleButton, self).__init__()
self.app_view = app_view
@@ -113,7 +114,7 @@ class AppsTable(QTableWidget):
if dialog.ask_confirmation(
title=self.i18n['manage_window.apps_table.row.actions.downgrade'],
body=self._parag(self.i18n['manage_window.apps_table.row.actions.downgrade.popup.body'].format(self._bold(str(pkg)))),
- locale_keys=self.i18n):
+ i18n=self.i18n):
self.window.downgrade(pkg)
action_downgrade.triggered.connect(downgrade)
@@ -131,7 +132,7 @@ class AppsTable(QTableWidget):
if dialog.ask_confirmation(
title=self.i18n[action.i18_label_key],
body=self._parag('{} {} ?'.format(self.i18n[action.i18_label_key], self._bold(str(pkg)))),
- locale_keys=self.i18n):
+ i18n=self.i18n):
self.window.execute_custom_action(pkg, action)
item.triggered.connect(custom_action)
@@ -162,7 +163,7 @@ class AppsTable(QTableWidget):
def _uninstall_app(self, app_v: PackageView):
if dialog.ask_confirmation(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'],
body=self._parag(self.i18n['manage_window.apps_table.row.actions.uninstall.popup.body'].format(self._bold(str(app_v)))),
- locale_keys=self.i18n):
+ i18n=self.i18n):
self.window.uninstall_app(app_v)
def _bold(self, text: str) -> str:
@@ -176,7 +177,7 @@ class AppsTable(QTableWidget):
if dialog.ask_confirmation(
title=self.i18n['manage_window.apps_table.row.actions.install.popup.title'],
body=self._parag(self.i18n['manage_window.apps_table.row.actions.install.popup.body'].format(self._bold(str(pkgv)))),
- locale_keys=self.i18n):
+ i18n=self.i18n):
self.window.install(pkgv)
diff --git a/bauh/view/qt/confirmation.py b/bauh/view/qt/confirmation.py
index 53980ccb..ceeee6ab 100644
--- a/bauh/view/qt/confirmation.py
+++ b/bauh/view/qt/confirmation.py
@@ -1,22 +1,23 @@
from typing import List
from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget
+
from bauh.api.abstract.view import ViewComponent, SingleSelectComponent, MultipleSelectComponent
from bauh.view.qt import css
-
from bauh.view.qt.components import MultipleSelectQt, new_single_select
+from bauh.view.util.translation import I18n
class ConfirmationDialog(QMessageBox):
- def __init__(self, title: str, body: str, locale_keys: dict, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None):
+ def __init__(self, title: str, body: str, i18n: I18n, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None):
super(ConfirmationDialog, self).__init__()
self.setWindowTitle(title)
self.setStyleSheet('QLabel { margin-right: 25px; }')
- self.bt_yes = self.addButton(locale_keys['popup.button.yes'] if not confirmation_label else confirmation_label.capitalize(), QMessageBox.YesRole)
+ self.bt_yes = self.addButton(i18n['popup.button.yes'] if not confirmation_label else confirmation_label.capitalize(), QMessageBox.YesRole)
self.bt_yes.setStyleSheet(css.OK_BUTTON)
- self.addButton(locale_keys['popup.button.no'] if not deny_label else deny_label.capitalize(), QMessageBox.NoRole)
+ self.addButton(i18n['popup.button.no'] if not deny_label else deny_label.capitalize(), QMessageBox.NoRole)
if body:
if not components:
diff --git a/bauh/view/qt/dialog.py b/bauh/view/qt/dialog.py
index ddf40304..2dbc1a08 100644
--- a/bauh/view/qt/dialog.py
+++ b/bauh/view/qt/dialog.py
@@ -6,6 +6,7 @@ from bauh.api.abstract.view import MessageType
from bauh.view.util import resource
from bauh.view.qt import css
+from bauh.view.util.translation import I18n
MSG_TYPE_MAP = {
MessageType.ERROR: QMessageBox.Critical,
@@ -26,7 +27,7 @@ def show_message(title: str, body: str, type_: MessageType, icon: QIcon = QIcon(
popup.exec_()
-def ask_confirmation(title: str, body: str, locale_keys: dict, icon: QIcon = QIcon(resource.get_path('img/logo.svg')), widgets: List[QWidget] = None):
+def ask_confirmation(title: str, body: str, i18n: I18n, icon: QIcon = QIcon(resource.get_path('img/logo.svg')), widgets: List[QWidget] = None):
diag = QMessageBox()
diag.setIcon(QMessageBox.Question)
diag.setWindowTitle(title)
@@ -42,10 +43,10 @@ def ask_confirmation(title: str, body: str, locale_keys: dict, icon: QIcon = QIc
diag.layout().addWidget(wbody, 0, 1)
- bt_yes = diag.addButton(locale_keys['popup.button.yes'], QMessageBox.YesRole)
+ bt_yes = diag.addButton(i18n['popup.button.yes'], QMessageBox.YesRole)
bt_yes.setStyleSheet(css.OK_BUTTON)
- diag.addButton(locale_keys['popup.button.no'], QMessageBox.NoRole)
+ diag.addButton(i18n['popup.button.no'], QMessageBox.NoRole)
if icon:
diag.setWindowIcon(icon)
diff --git a/bauh/view/qt/gem_selector.py b/bauh/view/qt/gem_selector.py
index be810f7f..2a2a4644 100644
--- a/bauh/view/qt/gem_selector.py
+++ b/bauh/view/qt/gem_selector.py
@@ -9,11 +9,12 @@ from bauh.view.core.controller import GenericSoftwareManager
from bauh.view.util import resource
from bauh.view.qt import qt_utils, css
from bauh.view.qt.components import MultipleSelectQt, CheckboxQt, new_spacer
+from bauh.view.util.translation import I18n
class GemSelectorPanel(QWidget):
- def __init__(self, window: QWidget, manager: GenericSoftwareManager, i18n: dict, config: Configuration, show_panel_after_restart: bool = False):
+ def __init__(self, window: QWidget, manager: GenericSoftwareManager, i18n: I18n, config: Configuration, show_panel_after_restart: bool = False):
super(GemSelectorPanel, self).__init__()
self.window = window
self.manager = manager
diff --git a/bauh/view/qt/history.py b/bauh/view/qt/history.py
index 78fe283c..2498f2cc 100644
--- a/bauh/view/qt/history.py
+++ b/bauh/view/qt/history.py
@@ -7,11 +7,12 @@ from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem
from bauh.api.abstract.cache import MemoryCache
from bauh.api.abstract.model import PackageHistory
+from bauh.view.util.translation import I18n
class HistoryDialog(QDialog):
- def __init__(self, history: PackageHistory, icon_cache: MemoryCache, i18n: dict):
+ def __init__(self, history: PackageHistory, icon_cache: MemoryCache, i18n: I18n):
super(HistoryDialog, self).__init__()
self.setWindowFlags(self.windowFlags() | Qt.WindowSystemMenuHint | Qt.WindowMinMaxButtonsHint)
diff --git a/bauh/view/qt/info.py b/bauh/view/qt/info.py
index b9bbb11c..1a19011a 100644
--- a/bauh/view/qt/info.py
+++ b/bauh/view/qt/info.py
@@ -4,13 +4,14 @@ from PyQt5.QtWidgets import QDialog, QVBoxLayout, QGroupBox, \
QLineEdit, QLabel, QGridLayout, QPushButton, QPlainTextEdit, QToolBar
from bauh.api.abstract.cache import MemoryCache
+from bauh.view.util.translation import I18n
IGNORED_ATTRS = {'name', '__app__'}
class InfoDialog(QDialog):
- def __init__(self, app: dict, icon_cache: MemoryCache, i18n: dict, screen_size: QSize()):
+ def __init__(self, app: dict, icon_cache: MemoryCache, i18n: I18n, screen_size: QSize()):
super(InfoDialog, self).__init__()
self.setWindowTitle(str(app['__app__']))
self.screen_size = screen_size
diff --git a/bauh/view/qt/root.py b/bauh/view/qt/root.py
index d4f16cb0..3638c816 100644
--- a/bauh/view/qt/root.py
+++ b/bauh/view/qt/root.py
@@ -7,13 +7,14 @@ from bauh.api.abstract.view import MessageType
from bauh.commons.system import new_subprocess
from bauh.view.qt.dialog import show_message
from bauh.view.util import resource
+from bauh.view.util.translation import I18n
def is_root():
return os.getuid() == 0
-def ask_root_password(i18n: dict):
+def ask_root_password(i18n: I18n):
diag = QInputDialog()
diag.setStyleSheet("""QLineEdit { border-radius: 5px; font-size: 16px }""")
diag.setInputMode(QInputDialog.TextInput)
diff --git a/bauh/view/qt/screenshots.py b/bauh/view/qt/screenshots.py
index 56bd13fc..f61712d3 100644
--- a/bauh/view/qt/screenshots.py
+++ b/bauh/view/qt/screenshots.py
@@ -12,6 +12,7 @@ from bauh.view.qt import qt_utils
from bauh.view.qt.components import new_spacer
from bauh.view.qt.thread import AnimateProgress
from bauh.view.qt.view_model import PackageView
+from bauh.view.util.translation import I18n
class ScreenshotsDialog(QDialog):
@@ -19,7 +20,7 @@ class ScreenshotsDialog(QDialog):
MAX_HEIGHT = 600
MAX_WIDTH = 800
- def __init__(self, pkg: PackageView, http_client: HttpClient, icon_cache: MemoryCache, i18n: dict, screenshots: List[QPixmap], logger: logging.Logger):
+ def __init__(self, pkg: PackageView, http_client: HttpClient, icon_cache: MemoryCache, i18n: I18n, screenshots: List[QPixmap], logger: logging.Logger):
super(ScreenshotsDialog, self).__init__()
self.setWindowTitle(str(pkg))
self.screenshots = screenshots
diff --git a/bauh/view/qt/styles.py b/bauh/view/qt/styles.py
index 9636b163..20e00313 100644
--- a/bauh/view/qt/styles.py
+++ b/bauh/view/qt/styles.py
@@ -5,11 +5,12 @@ from bauh.commons.html import bold
from bauh.view.core import config
from bauh.view.util import util
from bauh.view.qt import dialog
+from bauh.view.util.translation import I18n
class StylesComboBox(QComboBox):
- def __init__(self, parent: QWidget, i18n: dict, show_panel_after_restart: bool):
+ def __init__(self, parent: QWidget, i18n: I18n, show_panel_after_restart: bool):
super(StylesComboBox, self).__init__(parent=parent)
self.app = QApplication.instance()
self.styles = []
diff --git a/bauh/view/qt/systray.py b/bauh/view/qt/systray.py
index cb168f8f..201936c8 100755
--- a/bauh/view/qt/systray.py
+++ b/bauh/view/qt/systray.py
@@ -14,6 +14,7 @@ from bauh.api.abstract.model import PackageUpdate
from bauh.view.util import util, resource
from bauh.view.qt.about import AboutDialog
from bauh.view.qt.window import ManageWindow
+from bauh.view.util.translation import I18n
class UpdateCheck(QThread):
@@ -35,7 +36,7 @@ class UpdateCheck(QThread):
class TrayIcon(QSystemTrayIcon):
- def __init__(self, i18n: dict, manager: SoftwareManager, manage_window: ManageWindow, check_interval: int = 60, update_notification: bool = True):
+ def __init__(self, i18n: I18n, manager: SoftwareManager, manage_window: ManageWindow, check_interval: int = 60, update_notification: bool = True):
super(TrayIcon, self).__init__()
self.i18n = i18n
self.manager = manager
diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py
index f3be47e0..eacc72dc 100644
--- a/bauh/view/qt/thread.py
+++ b/bauh/view/qt/thread.py
@@ -12,9 +12,9 @@ from bauh.api.abstract.handler import ProcessWatcher
from bauh.api.abstract.model import PackageStatus, SoftwarePackage, PackageAction
from bauh.api.abstract.view import InputViewComponent, MessageType
from bauh.api.exception import NoInternetException
-from bauh.api.http import HttpClient
from bauh.view.qt import commons
from bauh.view.qt.view_model import PackageView
+from bauh.view.util.translation import I18n
RE_VERSION_IN_NAME = re.compile(r'\s+version\s+[\w\.]+\s*$')
@@ -76,12 +76,12 @@ class AsyncAction(QThread, ProcessWatcher):
class UpdateSelectedApps(AsyncAction):
- def __init__(self, manager: SoftwareManager, locale_keys: dict, apps_to_update: List[PackageView] = None):
+ def __init__(self, manager: SoftwareManager, i18n: I18n, apps_to_update: List[PackageView] = None):
super(UpdateSelectedApps, self).__init__()
self.apps_to_update = apps_to_update
self.manager = manager
self.root_password = None
- self.locale_keys = locale_keys
+ self.i18n = i18n
def run(self):
@@ -93,7 +93,7 @@ class UpdateSelectedApps(AsyncAction):
name = app.model.name if not RE_VERSION_IN_NAME.findall(app.model.name) else app.model.name.split('version')[0].strip()
- self.change_status('{} {} {}...'.format(self.locale_keys['manage_window.status.upgrading'], name, app.model.version))
+ self.change_status('{} {} {}...'.format(self.i18n['manage_window.status.upgrading'], name, app.model.version))
success = bool(self.manager.update(app.model, self.root_password, self))
self.change_substatus('')
@@ -168,11 +168,11 @@ class UninstallApp(AsyncAction):
class DowngradeApp(AsyncAction):
- def __init__(self, manager: SoftwareManager, locale_keys: dict, app: PackageView = None):
+ def __init__(self, manager: SoftwareManager, i18n: I18n, app: PackageView = None):
super(DowngradeApp, self).__init__()
self.manager = manager
self.app = app
- self.locale_keys = locale_keys
+ self.i18n = i18n
self.root_password = None
def run(self):
@@ -182,7 +182,7 @@ class DowngradeApp(AsyncAction):
success = self.manager.downgrade(self.app.model, self.root_password, self)
except (requests.exceptions.ConnectionError, NoInternetException) as e:
success = False
- self.print(self.locale_keys['internet.required'])
+ self.print(self.i18n['internet.required'])
finally:
self.notify_finished({'app': self.app, 'success': success})
self.app = None
@@ -206,18 +206,18 @@ class GetAppInfo(AsyncAction):
class GetAppHistory(AsyncAction):
- def __init__(self, manager: SoftwareManager, locale_keys: dict, app: PackageView = None):
+ def __init__(self, manager: SoftwareManager, i18n: I18n, app: PackageView = None):
super(GetAppHistory, self).__init__()
self.app = app
self.manager = manager
- self.locale_keys = locale_keys
+ self.i18n = i18n
def run(self):
if self.app:
try:
self.notify_finished({'history': self.manager.get_history(self.app.model)})
except (requests.exceptions.ConnectionError, NoInternetException) as e:
- self.notify_finished({'error': self.locale_keys['internet.required']})
+ self.notify_finished({'error': self.i18n['internet.required']})
finally:
self.app = None
@@ -246,13 +246,13 @@ class SearchPackages(AsyncAction):
class InstallPackage(AsyncAction):
- def __init__(self, manager: SoftwareManager, disk_cache: bool, icon_cache: MemoryCache, locale_keys: dict, pkg: PackageView = None):
+ def __init__(self, manager: SoftwareManager, disk_cache: bool, icon_cache: MemoryCache, i18n: I18n, pkg: PackageView = None):
super(InstallPackage, self).__init__()
self.pkg = pkg
self.manager = manager
self.icon_cache = icon_cache
self.disk_cache = disk_cache
- self.locale_keys = locale_keys
+ self.i18n = i18n
self.root_password = None
def run(self):
@@ -272,7 +272,7 @@ class InstallPackage(AsyncAction):
only_icon=False)
except (requests.exceptions.ConnectionError, NoInternetException):
success = False
- self.print(self.locale_keys['internet.required'])
+ self.print(self.i18n['internet.required'])
finally:
self.signal_finished.emit({'success': success, 'pkg': self.pkg})
self.pkg = None
@@ -403,9 +403,9 @@ class ListWarnings(QThread):
signal_warnings = pyqtSignal(list)
- def __init__(self, man: SoftwareManager, locale_keys: dict):
+ def __init__(self, man: SoftwareManager, i18n: I18n):
super(QThread, self).__init__()
- self.locale_keys = locale_keys
+ self.i18n = i18n
self.man = man
def run(self):
@@ -464,12 +464,13 @@ class ApplyFilters(AsyncAction):
class CustomAction(AsyncAction):
- def __init__(self, manager: SoftwareManager, custom_action: PackageAction = None, pkg: PackageView = None, root_password: str = None):
+ def __init__(self, manager: SoftwareManager, i18n: I18n, custom_action: PackageAction = None, pkg: PackageView = None, root_password: str = None):
super(CustomAction, self).__init__()
self.manager = manager
self.pkg = pkg
self.custom_action = custom_action
self.root_password = root_password
+ self.i18n = i18n
def run(self):
success = True
@@ -481,7 +482,7 @@ class CustomAction(AsyncAction):
watcher=self)
except (requests.exceptions.ConnectionError, NoInternetException):
success = False
- self.signal_output.emit(self.locale_keys['internet.required'])
+ self.signal_output.emit(self.i18n['internet.required'])
self.notify_finished({'success': success, 'pkg': self.pkg})
self.pkg = None
diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py
index 16da819d..334816a2 100755
--- a/bauh/view/qt/window.py
+++ b/bauh/view/qt/window.py
@@ -36,6 +36,7 @@ from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, D
from bauh.view.qt.view_model import PackageView
from bauh.view.qt.view_utils import load_icon
from bauh.view.util import util, resource
+from bauh.view.util.translation import I18n
DARK_ORANGE = '#FF4500'
@@ -50,7 +51,7 @@ class ManageWindow(QWidget):
signal_user_res = pyqtSignal(bool)
signal_table_update = pyqtSignal()
- def __init__(self, i18n: dict, icon_cache: MemoryCache, manager: SoftwareManager, disk_cache: bool,
+ def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, disk_cache: bool,
download_icons: bool, screen_size, suggestions: bool, display_limit: int, config: Configuration,
context: ApplicationContext, notifications: bool, http_client: HttpClient, logger: logging.Logger,
tray_icon=None):
@@ -231,7 +232,7 @@ class ManageWindow(QWidget):
self.thread_downgrade = self._bind_async_action(DowngradeApp(self.manager, self.i18n), finished_call=self._finish_downgrade)
self.thread_suggestions = self._bind_async_action(FindSuggestions(man=self.manager), finished_call=self._finish_search, only_finished=True)
self.thread_run_app = self._bind_async_action(LaunchApp(self.manager), finished_call=self._finish_run_app, only_finished=False)
- self.thread_custom_action = self._bind_async_action(CustomAction(manager=self.manager), finished_call=self._finish_custom_action)
+ self.thread_custom_action = self._bind_async_action(CustomAction(manager=self.manager, i18n=self.i18n), finished_call=self._finish_custom_action)
self.thread_screenshots = self._bind_async_action(GetScreenshots(self.manager), finished_call=self._finish_get_screenshots)
self.thread_apply_filters = ApplyFilters()
@@ -239,7 +240,7 @@ class ManageWindow(QWidget):
self.thread_apply_filters.signal_table.connect(self._update_table_and_upgrades)
self.signal_table_update.connect(self.thread_apply_filters.stop_waiting)
- self.thread_install = InstallPackage(manager=self.manager, disk_cache=self.disk_cache, icon_cache=self.icon_cache, locale_keys=self.i18n)
+ self.thread_install = InstallPackage(manager=self.manager, disk_cache=self.disk_cache, icon_cache=self.icon_cache, i18n=self.i18n)
self._bind_async_action(self.thread_install, finished_call=self._finish_install)
self.thread_animate_progress = AnimateProgress()
@@ -289,7 +290,7 @@ class ManageWindow(QWidget):
self.dialog_about = None
self.first_refresh = suggestions
- self.thread_warnings = ListWarnings(man=manager, locale_keys=i18n)
+ self.thread_warnings = ListWarnings(man=manager, i18n=i18n)
self.thread_warnings.signal_warnings.connect(self._show_warnings)
def set_tray_icon(self, tray_icon):
@@ -349,7 +350,7 @@ class ManageWindow(QWidget):
self.thread_animate_progress.pause()
diag = ConfirmationDialog(title=msg['title'],
body=msg['body'],
- locale_keys=self.i18n,
+ i18n=self.i18n,
components=msg['components'],
confirmation_label=msg['confirmation_label'],
deny_label=msg['deny_label'])
@@ -792,7 +793,7 @@ class ManageWindow(QWidget):
if to_update and dialog.ask_confirmation(title=self.i18n['manage_window.upgrade_all.popup.title'],
body=self.i18n['manage_window.upgrade_all.popup.body'],
- locale_keys=self.i18n,
+ i18n=self.i18n,
widgets=[UpdateToggleButton(None, self, self.i18n, clickable=False)]):
pwd = None
diff --git a/bauh/view/util/translation.py b/bauh/view/util/translation.py
new file mode 100644
index 00000000..ae3a349d
--- /dev/null
+++ b/bauh/view/util/translation.py
@@ -0,0 +1,67 @@
+import glob
+import locale
+from typing import Tuple
+
+from bauh.view.util import resource
+
+
+class I18n(dict):
+
+ def __init__(self, current_locale: dict, default_locale: dict):
+ super(I18n, self).__init__()
+ self.current = current_locale
+ self.default = default_locale
+
+ def __getitem__(self, item):
+ try:
+ return self.current.__getitem__(item)
+ except KeyError:
+ if self.default:
+ return self.default.__getitem__(item)
+ else:
+ raise
+
+ def get(self, *args, **kwargs):
+ res = self.current.get(args[0])
+
+ if res is None:
+ if self.default:
+ return self.default.get(*args, **kwargs)
+ else:
+ return self.current.get(*args, **kwargs)
+
+ return res
+
+
+def get_locale_keys(key: str = None, locale_dir: str = resource.get_path('locale')) -> Tuple[str, dict]:
+
+ locale_path = None
+
+ if key is None:
+ current_locale = locale.getdefaultlocale()
+ else:
+ current_locale = [key.strip().lower()]
+
+ if current_locale:
+ current_locale = current_locale[0]
+
+ for locale_file in glob.glob(locale_dir + '/*'):
+ name = locale_file.split('/')[-1]
+
+ if current_locale == name or current_locale.startswith(name + '_'):
+ locale_path = locale_file
+ break
+
+ if not locale_path:
+ return key, {}
+
+ with open(locale_path, 'r') as f:
+ locale_keys = f.readlines()
+
+ locale_obj = {}
+ for line in locale_keys:
+ if line:
+ keyval = line.strip().split('=')
+ locale_obj[keyval[0].strip()] = keyval[1].strip()
+
+ return locale_path.split('/')[-1], locale_obj
diff --git a/bauh/view/util/util.py b/bauh/view/util/util.py
index f4f3efc7..c4ffb795 100644
--- a/bauh/view/util/util.py
+++ b/bauh/view/util/util.py
@@ -1,9 +1,6 @@
-import glob
-import locale
import os
import subprocess
import sys
-from typing import Tuple
from PyQt5.QtCore import QCoreApplication
@@ -12,40 +9,6 @@ from bauh.commons.system import run_cmd
from bauh.view.util import resource
-def get_locale_keys(key: str = None, locale_dir: str = resource.get_path('locale')) -> Tuple[str, dict]:
-
- locale_path = None
-
- if key is None:
- current_locale = locale.getdefaultlocale()
- else:
- current_locale = [key.strip().lower()]
-
- if current_locale:
- current_locale = current_locale[0]
-
- for locale_file in glob.glob(locale_dir + '/*'):
- name = locale_file.split('/')[-1]
-
- if current_locale == name or current_locale.startswith(name + '_'):
- locale_path = locale_file
- break
-
- if not locale_path:
- locale_path = resource.get_path('locale/en')
-
- with open(locale_path, 'r') as f:
- locale_keys = f.readlines()
-
- locale_obj = {}
- for line in locale_keys:
- if line:
- keyval = line.strip().split('=')
- locale_obj[keyval[0].strip()] = keyval[1].strip()
-
- return locale_path.split('/')[-1], locale_obj
-
-
def notify_user(msg: str, icon_path: str = resource.get_path('img/logo.svg')):
os.system("notify-send -a {} {} '{}'".format(__app_name__, "-i {}".format(icon_path) if icon_path else '', msg))