diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 8a371ffa8..488053c90 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,4 +1,5 @@
To help us diagnose the problem quickly, please provide the output of the console command `backintime --diagnostics`.
-Additionally, please specify as precisely as you can the package or installation source where you got BackInTime. Sometimes there are multiple alternatives, like [for Arch-based distros](https://aur.archlinux.org/packages?K=backintime).
+Additionally, please specify as precisely as you can the package or installation source where you got Back In Time from. Sometimes there are multiple alternatives, like in [for Arch-based distros](https://aur.archlinux.org/packages?K=backintime).
+As an alternative fell free to use our mailing list for every topic about Back In Time. Visit the subscribtion page at https://mail.python.org/mailman3/lists/bit-dev.python.org or send an email with subject "Subscribe" to bit-dev-join@python.org.
diff --git a/CHANGES b/CHANGES
index 6fbb5f746..7d08af1bc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -33,6 +33,7 @@ Version 1.3.4-dev (development of upcoming release)
* Translation: Plural forms support (#1488).
* Removed: Translation in Canadian English, British English and Javanese (#1455).
* Added: Translation in Persian and Vietnamese (#1460).
+* Added: Message to users (after 10 starts of BIT Gui) to motivate them contributing translations (#1473).
Version 1.3.3 (2023-01-04)
* Feature: New command line argument "--diagnostics" to show helpful info for better issue support (#1100)
diff --git a/README.md b/README.md
index 2cb026ed9..28db0cb35 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,10 @@ and [help wanted](https://github.com/bit-team/backintime/issues?q=is%3Aissue+is%
* [FAQ - Frequently Asked Questions](FAQ.md)
* [Source code documentation for developers](https://backintime-dev.readthedocs.org)
* Use [Issues](https://github.com/bit-team/backintime/issues) to ask questions and report bugs.
- * [Mailing list _bit-dev_](https://mail.python.org/mailman3/lists/bit-dev.python.org/)
+ * [Mailing list
+ _bit-dev_](https://mail.python.org/mailman3/lists/bit-dev.python.org/) for
+ **every topic**, question and idea about _Back In Time_. Despite its name
+ it is not restricted to development topics only.
## Installation
diff --git a/common/config.py b/common/config.py
index c844ff46a..32b38f680 100644
--- a/common/config.py
+++ b/common/config.py
@@ -47,7 +47,7 @@
# The bigger problem with config.py is that it do use translatebale strings.
# Strings like this do not belong into a config file or its context.
try:
- _('Foo')
+ _('Cancel')
except NameError:
_ = lambda val: val
@@ -568,6 +568,27 @@ def language(self) -> str:
def setLanguage(self, language: str):
self.setStrValue('global.language', language if language else '')
+ def manual_starts_countdown(self) -> int:
+ """Countdown value about how often the users startet the Back In Time
+ GUI.
+
+ It is an internal variable not meant to be used or manipulated be the
+ users. At the end of the countown the
+ :py:class:`ApproachTranslatorDialog` is presented to the user.
+
+ """
+ return self.intValue('internal.manual_starts_countdown', 10)
+
+ def decrement_manual_starts_countdown(self):
+ """Counts down to -1.
+
+ See :py:func:`manual_starts_countdown()` for details.
+ """
+ val = self.manual_starts_countdown()
+
+ if val > -1:
+ self.setIntValue('internal.manual_starts_countdown', val - 1)
+
# SSH
def sshSnapshotsPath(self, profile_id = None):
#?Snapshot path on remote host. If the path is relative (no leading '/')
diff --git a/common/languages.py b/common/languages.py
index b34828d25..aed9e1c12 100644
--- a/common/languages.py
+++ b/common/languages.py
@@ -2084,6 +2084,7 @@
'da': 25,
'de': 99,
'el': 12,
+ 'en': 100,
'eo': 34,
'es': 55,
'et': 23,
diff --git a/common/tools.py b/common/tools.py
index 0305fc101..479096947 100644
--- a/common/tools.py
+++ b/common/tools.py
@@ -196,24 +196,24 @@ def get_available_language_codes():
# full path of one mo-file
# e.g. /usr/share/locale/de/LC_MESSAGES/backintime.mo
- po = gettext.find(domain=_GETTEXT_DOMAIN, localedir=_GETTEXT_LOCALE_DIR)
+ mo = gettext.find(domain=_GETTEXT_DOMAIN, localedir=_GETTEXT_LOCALE_DIR)
- if po:
- po = pathlib.Path(po)
+ if mo:
+ mo = pathlib.Path(mo)
else:
# Workaround. This happens if LC_ALL=C and BIT don't use an explicite
# language. Should be re-design.
- po = _GETTEXT_LOCALE_DIR / 'xy' / 'LC_MESSAGES' / 'backintime.po'
+ mo = _GETTEXT_LOCALE_DIR / 'xy' / 'LC_MESSAGES' / 'backintime.mo'
# e.g. de/LC_MESSAGES/backintime.mo
- po = po.relative_to(_GETTEXT_LOCALE_DIR)
+ mo = mo.relative_to(_GETTEXT_LOCALE_DIR)
# e.g. */LC_MESSAGES/backintime.mo
- po = pathlib.Path('*') / pathlib.Path(*po.parts[1:])
+ mo = pathlib.Path('*') / pathlib.Path(*mo.parts[1:])
- pofiles = _GETTEXT_LOCALE_DIR.rglob(str(po))
+ mofiles = _GETTEXT_LOCALE_DIR.rglob(str(mo))
- return [p.relative_to(_GETTEXT_LOCALE_DIR).parts[0] for p in pofiles]
+ return [p.relative_to(_GETTEXT_LOCALE_DIR).parts[0] for p in mofiles]
def get_language_names(language_code):
@@ -263,6 +263,23 @@ def get_language_names(language_code):
return result
+def get_native_language_and_completeness(language_code):
+ """Return the language name in its native flavour and the completeness of
+ its translation in percent.
+
+ Args:
+ language_code(str): The language code.
+
+ Returns:
+ A two-entry tuple with language name as string and a percent as
+ integer.
+ """
+ name = languages.names[language_code][language_code]
+ completeness = languages.completeness[language_code]
+
+ return (name, completeness)
+
+
# |------------------------------------|
# | Miscellaneous, not categorized yet |
# |------------------------------------|
diff --git a/qt/app.py b/qt/app.py
index e010d86c6..eb0e1ce0a 100644
--- a/qt/app.py
+++ b/qt/app.py
@@ -94,7 +94,7 @@
import snapshotsdialog
import logviewdialog
from restoredialog import RestoreDialog
-from languagedialog import LanguageDialog
+import languagedialog
import messagebox
@@ -400,6 +400,21 @@ def __init__(self, config, appInstance, qapp):
SetupCron(self).start()
+ # Finished countdown of manual GUI starts
+ if 0 == self.config.manual_starts_countdown():
+
+ # Do nothing if English is the current used language
+ if self.config.language_used != 'en':
+
+ # Show the message only if teh current used language is
+ # translated equal or less then 97%
+ self._open_approach_translator_dialog(cutoff=97)
+
+ # BIT counts down how often the GUI was started. Until the end of that
+ # countdown a dialog with a text about contributing to translating
+ # BIT is prestented to the users.
+ self.config.decrement_manual_starts_countdown()
+
@property
def showHiddenFiles(self):
return self.config.boolValue('qt.show_hidden_files', False)
@@ -473,7 +488,7 @@ def _create_actions(self):
self.btnLastLogViewClicked, None,
None),
'act_settings': (
- icon.SETTINGS, _('Manage profiles…'),
+ icon.SETTINGS, '{}…'.format(_('Manage profiles')),
self.btnSettingsClicked, ['Ctrl+Shift+P'],
None),
'act_shutdown': (
@@ -481,7 +496,7 @@ def _create_actions(self):
None, None,
_('Shut down system after snapshot has finished.')),
'act_setup_language': (
- None, _('Setup language…'),
+ None, '{}…'.format(_('Setup language')),
self.slot_setup_language, None,
None),
'act_quit': (
@@ -510,6 +525,9 @@ def _create_actions(self):
'act_help_bugreport': (
icon.BUG, _('Report a bug'),
self.btnReportBugClicked, None, None),
+ 'act_help_translation': (
+ None, _('Translation'),
+ self.slot_help_translation, None, None),
'act_help_about': (
icon.ABOUT, _('About'),
self.btnAboutClicked, None, None),
@@ -540,7 +558,7 @@ def _create_actions(self):
icon.SHOW_HIDDEN, _('Show hidden files'),
None, ['Ctrl+H'], None),
'act_snapshots_dialog': (
- icon.SNAPSHOTS, _('Compare snapshots…'),
+ icon.SNAPSHOTS, '{}…'.format(_('Compare snapshots')),
self.btnSnapshotsClicked, None, None),
}
@@ -626,6 +644,7 @@ def _create_menubar(self):
self.act_help_faq,
self.act_help_question,
self.act_help_bugreport,
+ self.act_help_translation,
self.act_help_about,
)
}
@@ -1750,6 +1769,16 @@ def suspendMouseButtonNavigation(self):
yield
self.setMouseButtonNavigation()
+ def _open_approach_translator_dialog(self, cutoff=101):
+ code = self.config.language_used
+ name, perc = tools.get_native_language_and_completeness(code)
+
+ if perc > cutoff:
+ return
+
+ dlg = languagedialog.ApproachTranslatorDialog(self, name, perc)
+ dlg.exec()
+
# |-------|
# | Slots |
# |-------|
@@ -1757,13 +1786,14 @@ def slot_setup_language(self):
"""Show a modal language settings dialog and modify the UI language
settings."""
- dlg = LanguageDialog(
+ dlg = languagedialog.LanguageDialog(
used_language_code=self.config.language_used,
configured_language_code=self.config.language())
dlg.exec()
- if dlg.result() == 1 and self.config.language != dlg.language_code:
+ # Apply/OK pressed & the language value modified
+ if dlg.result() == 1 and self.config.language() != dlg.language_code:
self.config.setLanguage(dlg.language_code)
@@ -1771,6 +1801,9 @@ def slot_setup_language(self):
'restarting Back In Time.'),
widget_to_center_on=dlg)
+ def slot_help_translation(self):
+ self._open_approach_translator_dialog()
+
class About(QDialog):
def __init__(self, parent = None):
diff --git a/qt/languagedialog.py b/qt/languagedialog.py
index 128d6dcd4..c25b19c02 100644
--- a/qt/languagedialog.py
+++ b/qt/languagedialog.py
@@ -1,17 +1,20 @@
-import locale
+import textwrap
from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import (QApplication,
QDialog,
QWidget,
+ QTabWidget,
QScrollArea,
QGridLayout,
QVBoxLayout,
QDialogButtonBox,
QRadioButton,
+ QLabel,
+ QToolTip,
)
import tools
import qttools
-import logger
import languages
@@ -22,7 +25,7 @@ def __init__(self, used_language_code: str, configured_language_code: str):
self.used_language_code = used_language_code
self.configured_language_code = configured_language_code
- self.setWindowTitle(_('Language selection'))
+ self.setWindowTitle(_('Setup language'))
self.setWindowFlag(Qt.WindowMaximizeButtonHint, True)
scroll = QScrollArea(self)
@@ -37,12 +40,15 @@ def __init__(self, used_language_code: str, configured_language_code: str):
new_width = self._calculate_scroll_area_width()
self._scroll.setMinimumWidth(new_width)
- button = QDialogButtonBox(QDialogButtonBox.Apply, self)
- button.clicked.connect(self.accept)
+ buttonbox = QDialogButtonBox(
+ QDialogButtonBox.Cancel | QDialogButtonBox.Ok, self)
+
+ buttonbox.accepted.connect(self.accept)
+ buttonbox.rejected.connect(self.reject)
layout = QVBoxLayout(self)
layout.addWidget(scroll)
- layout.addWidget(button)
+ layout.addWidget(buttonbox)
def _calculate_scroll_area_width(self):
"""Credits:
@@ -61,11 +67,13 @@ def _create_radio_button(self, lang_code, label, tooltip) -> QRadioButton:
r.lang_code = lang_code
# Is it the current used AND configured language?
- if r.lang_code == self.used_language_code and r.lang_code == self.configured_language_code:
+ if (r.lang_code == self.used_language_code
+ and r.lang_code == self.configured_language_code):
+
r.setChecked(True)
# "System default"
- elif self.configured_language_code == '' and r.lang_code == None:
+ elif self.configured_language_code == '' and r.lang_code is None:
r.setChecked(True)
return r
@@ -134,7 +142,7 @@ def _language_widget(self):
tooltip = '{}\n{}'.format(
tooltip,
_('Translated: {percent}').format(
- percent=f'{languages.completeness[code]}%')
+ percent=f'{complete}%')
)
# Create button
@@ -156,3 +164,81 @@ def slot_radio(self, val):
if btn.isChecked():
self.language_code = btn.lang_code
+
+
+class ApproachTranslatorDialog(QDialog):
+ """Prestens a message to the users to motivate them contributing to the
+ translation of Back In Time.
+ """
+
+ # ToDo (2023-08): Move to packages meta-data (pyproject.toml).
+ _URL_PLATFORM = 'https://translate.codeberg.org/engage/backintime'
+ _URL_PROJECT = 'https://github.com/bit-team/backintime'
+
+ @staticmethod
+ def _complete_text(language, percent):
+
+ # Note: The length of the variable names in that string are on
+ # purpose. It is relevant when wrapping the text.
+ txt = _(
+ 'Hello'
+ '\n'
+ 'You have used Back In Time in the {language} '
+ 'language a few times by now.'
+ '\n'
+ 'The translation of your installed version of Back In Time '
+ 'into {language} is {perc} complete. Regardless of your '
+ 'level of technical expertise, you can contribute to the '
+ 'translation and thus Back In Time itself.'
+ '\n'
+ 'Please visit the {translation_platform_url} if you wish '
+ 'to contribute. For further assistance and questions, '
+ 'please visit the {back_in_time_project_website}.'
+ '\n'
+ 'We apologize for the interruption, and this message '
+ 'will not be shown again. This dialog is available at '
+ 'any time via the help menu.'
+ '\n'
+ 'Your Back In Time Team.'
+ )
+
+ # Wrap the lines, insert
tag as linebreak and wrap paragraphs in
+ #
tags. + result = '' + for t in txt.split('\n'): + result += '
' + '
'.join(textwrap.wrap(t, width=60)) + '