Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Language in config file #1493

Merged
merged 5 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Version 1.3.4-dev (development of upcoming release)
* Breaking change: Minimal Python version 3.8 required (#1358).
* Removed: Handling and checking of user group "fuse" (#1472).
* Feature: Exclude /swapfile by default (#1053)
# Feature: Rearranged menu bar and its entries in the main window (#1487, #1478).
* Feature: Rearranged menu bar and its entries in the main window (#1487, #1478).
* Feature: Configure user interface language via config file.
* Documentation: Removed outdated docbook (#1345).
* Build: Introduced .readthedocs.yaml as asked by ReadTheDocs.org (#1443).
* Dependency: The oxygen icons should be installed with the BiT Qt GUI since they are used as fallback in case of missing icons
Expand Down
2 changes: 1 addition & 1 deletion common/backintime.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import tools
# Workaround for situations where startApp() is not invoked.
# E.g. when using --diagnostics and other argparse.Action
tools.initiate_translation()
tools.initiate_translation(None)

import config
import logger
Expand Down
18 changes: 17 additions & 1 deletion common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ class Config(configfile.ConfigFileWithProfiles):
PLUGIN_MANAGER = pluginmanager.PluginManager()

def __init__(self, config_path=None, data_path=None):
# Note: The main profiles name here is translated using the systems
# current locale because the language code in the config file wasn't
# read yet.
configfile.ConfigFileWithProfiles.__init__(self, _('Main profile'))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future we should remove the ability to translate the first profiles name. A name as an identifier shouldn't be translated at all. Related to #1371

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or even better: Why do we need a default profile at all?


self._APP_PATH = tools.backintimePath()
Expand Down Expand Up @@ -326,7 +329,10 @@ def __init__(self, config_path=None, data_path=None):
self.inhibitCookie = None
self.setupUdev = tools.SetupUdev()

tools.initiate_translation()
tools.initiate_translation(self.language())

# Workaround
self.default_profile_name = _('Main profile')
buhtz marked this conversation as resolved.
Show resolved Hide resolved

def save(self):
self.setIntValue('config.version', self.CONFIG_VERSION)
Expand Down Expand Up @@ -546,6 +552,16 @@ def incrementHashCollision(self):
value = self.hashCollision() + 1
self.setIntValue('global.hash_collision', value)

def language(self) -> str:
#?Language code (ISO 639) used to translate the user interface.
#?If empty the operating systems current local is used. If 'en' the
#?translation is not active and the original English source strings
#?are used. It is the same if the value is unknown.
return self.strValue('global.language', '')

def setLanguage(self, language: str):
self.setStrValue('globl.language', language)

# SSH
def sshSnapshotsPath(self, profile_id = None):
#?Snapshot path on remote host. If the path is relative (no leading '/')
Expand Down
11 changes: 10 additions & 1 deletion common/man/C/backintime-config.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.TH backintime-config 1 "May 2023" "version 1.3.4-dev" "USER COMMANDS"
.TH backintime-config 1 "Aug 2023" "version 1.3.4-dev" "USER COMMANDS"
.SH NAME
config \- BackInTime configuration files.
.SH SYNOPSIS
Expand Down Expand Up @@ -37,6 +37,15 @@ Internal value used to prevent hash collisions on mountpoints. Do not change thi
Default: 0
.RE

.IP "\fIglobal.language\fR" 6
.RS
Type: str Allowed Values: text
.br
Language code (ISO 639) used to translate the user interface. If empty the operating systems current local is used. If 'en' the translation is not active and the original English source strings are used. It is the same if the value is unknown.
.PP
Default: ''
.RE

.IP "\fIglobal.use_flock\fR" 6
.RS
Type: bool Allowed Values: true|false
Expand Down
10 changes: 7 additions & 3 deletions common/password.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,13 @@ def passwordFromUser(self, parent, profile_id = None, mode = None, pw_id = 1, pr
password = ''
return password

password = messagebox.askPasswordDialog(parent, self.config.APP_NAME,
prompt = prompt,
timeout = 300)
password = messagebox.askPasswordDialog(
parent=parent,
title=self.config.APP_NAME,
prompt=prompt,
language_code=self.config.language(),
timeout=300)

return password

def setPasswordDb(self, service_name, user_name, password):
Expand Down
2 changes: 1 addition & 1 deletion common/test/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import logger
import tools
# Needed because backintime.startApp() is not invoked.
tools.initiate_translation()
tools.initiate_translation(None)
import config
import snapshots

Expand Down
18 changes: 13 additions & 5 deletions common/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,22 @@ def sharePath():
_GETTEXT_LOCALE_DIR = os.path.join(sharePath(), 'locale')


def initiate_translation(language_code: str = None):
def initiate_translation(language_code: str):
"""Initiate Class-based API of GNU gettext.

Args:
language_code: Language code to use (based on ISO-639-1).

It installs the ``_()`` in the ``builtins`` namespace and eliminates the
need to ``import gettext`` and declare ``_()`` in each module. The systems
current local is used if no language code is provided.
It installs the ``_()`` (and ``ngettext()`` for plural forms) in the
``builtins`` namespace and eliminates the need to ``import gettext``
and declare ``_()`` in each module. The systems current local is used
if the language code is None.
"""
# language_code = 'ja' # DEBUG

if language_code:
logger.debug(f'Language code "{language_code}".')
else:
logger.debug(f'No language code. Use systems current locale.')

translation = gettext.translation(
domain=_GETTEXT_DOMAIN,
Expand All @@ -124,6 +129,9 @@ def initiate_translation(language_code: str = None):
)
translation.install(names=['ngettext'])

# logger.debug('Translate test: "{}" -> "{}"'
# .format('Disabled', _('Disabled')))


# |------------------------------------|
# | Miscellaneous, not categorized yet |
Expand Down
4 changes: 2 additions & 2 deletions qt/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

# Workaround until the codebase is rectified/equalized.
import tools
tools.initiate_translation()
tools.initiate_translation(None)

import qttools

Expand Down Expand Up @@ -1896,7 +1896,7 @@ def debugTrace():

logger.openlog()
qapp = qttools.createQApplication(cfg.APP_NAME)
translator = qttools.translator()
translator = qttools.initiate_translator(cfg.language())
qapp.installTranslator(translator)

mainWindow = MainWindow(cfg, appInstance, qapp)
Expand Down
4 changes: 2 additions & 2 deletions qt/messagebox.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
import qttools


def askPasswordDialog(parent, title, prompt, timeout = None):
def askPasswordDialog(parent, title, prompt, language_code, timeout):
if parent is None:
app = qttools.createQApplication()
translator = qttools.translator()
translator = qttools.initate_translator(language_code)
app.installTranslator(translator)

import icon
Expand Down
2 changes: 1 addition & 1 deletion qt/qtsystrayicon.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __init__(self):
%sys.argv[1], self)

self.qapp = qttools.createQApplication(self.config.APP_NAME)
translator = qttools.translator()
translator = qttools.initiate_translator(self.config.language())
self.qapp.installTranslator(translator)
self.qapp.setQuitOnLastWindowClosed(False)

Expand Down
21 changes: 16 additions & 5 deletions qt/qttools.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,24 +263,35 @@ def createQApplication(app_name='Back In Time'):
return qapp


def translator(language_code: str = None) -> QTranslator:
def initiate_translator(language_code: str) -> QTranslator:
"""Creating an Qt related translator.

Args:
language_code: Language code to use (based on ISO-639-1).

This is done beside the primarily used GNU gettext because Qt need to
translate its own default elements like Yes/No-buttons. The systems
current local is used when no language code is provided.
current local is used when no language code is provided. Translation is
deactivated if language code is unknown.
"""

translator = QTranslator()

if not language_code:
if language_code:
logger.debug(f'Language code "{language_code}".')
else:
logger.debug(f'No language code. Use systems current locale.')
language_code = QLocale.system().name()

translator.load(f'qt_{language_code}',
QLibraryInfo.location(QLibraryInfo.TranslationsPath))
rc = translator.load(
f'qt_{language_code}',
QLibraryInfo.location(QLibraryInfo.TranslationsPath))

if rc == False:
logger.warning(
'PyQt was not able to install a translator for language code '
f'"{language_code}". Deactivate translation and falling back to '
'the source language (English).')

return translator

Expand Down