[Core] Refactor the base argparser and translation code.

- Move baseargparser out of deluge/ui since it is also used by the
  Daemon and could cause packaging issues if UI code is not available.
  - Renamed baseargparser to argparserbase to follow existing Deluge
    naming.
  - Renamed get_version to distinguish from deluge.common.get_version.
- Translation code is usable by more than just the UIs so also move it
  to Deluge namespace and re-use i18n directory and make it a package.
  - Renamed setup_translations to singular as it felt more correct.
  - Renamed set_dummy_trans to be more descriptive.

Closes: #3081
This commit is contained in:
Calum Lind 2019-05-10 12:58:33 +01:00
commit d417c4b0f9
18 changed files with 82 additions and 62 deletions

View file

@ -86,7 +86,7 @@ argparse.ArgumentParser.find_subcommand = find_subcommand
argparse.ArgumentParser.set_default_subparser = set_default_subparser argparse.ArgumentParser.set_default_subparser = set_default_subparser
def get_version(): def _get_version_detail():
version_str = '%s\n' % (common.get_version()) version_str = '%s\n' % (common.get_version())
try: try:
from deluge._libtorrent import LT_VERSION from deluge._libtorrent import LT_VERSION
@ -151,7 +151,7 @@ class HelpAction(argparse._HelpAction):
parser.exit() parser.exit()
class BaseArgParser(argparse.ArgumentParser): class ArgParserBase(argparse.ArgumentParser):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if 'formatter_class' not in kwargs: if 'formatter_class' not in kwargs:
kwargs['formatter_class'] = lambda prog: DelugeTextHelpFormatter( kwargs['formatter_class'] = lambda prog: DelugeTextHelpFormatter(
@ -165,7 +165,7 @@ class BaseArgParser(argparse.ArgumentParser):
self.log_stream = kwargs['log_stream'] self.log_stream = kwargs['log_stream']
del kwargs['log_stream'] del kwargs['log_stream']
super(BaseArgParser, self).__init__(*args, **kwargs) super(ArgParserBase, self).__init__(*args, **kwargs)
self.common_setup = False self.common_setup = False
self.process_arg_group = False self.process_arg_group = False
@ -178,13 +178,13 @@ class BaseArgParser(argparse.ArgumentParser):
'-V', '-V',
'--version', '--version',
action='version', action='version',
version='%(prog)s ' + get_version(), version='%(prog)s ' + _get_version_detail(),
help=_('Print version information'), help=_('Print version information'),
) )
self.group.add_argument( self.group.add_argument(
'-v', '-v',
action='version', action='version',
version='%(prog)s ' + get_version(), version='%(prog)s ' + _get_version_detail(),
help=argparse.SUPPRESS, help=argparse.SUPPRESS,
) # Deprecated arg ) # Deprecated arg
self.group.add_argument( self.group.add_argument(
@ -246,7 +246,7 @@ class BaseArgParser(argparse.ArgumentParser):
argparse.Namespace: The parsed arguments. argparse.Namespace: The parsed arguments.
""" """
options = super(BaseArgParser, self).parse_args(args=args) options = super(ArgParserBase, self).parse_args(args=args)
return self._handle_ui_options(options) return self._handle_ui_options(options)
def parse_known_ui_args(self, args, withhold=None): def parse_known_ui_args(self, args, withhold=None):
@ -262,7 +262,7 @@ class BaseArgParser(argparse.ArgumentParser):
""" """
if withhold: if withhold:
args = [a for a in args if a not in withhold] args = [a for a in args if a not in withhold]
options, remaining = super(BaseArgParser, self).parse_known_args(args=args) options, remaining = super(ArgParserBase, self).parse_known_args(args=args)
options.remaining = remaining options.remaining = remaining
# Hanlde common and process group options # Hanlde common and process group options
return self._handle_ui_options(options) return self._handle_ui_options(options)

View file

@ -15,10 +15,10 @@ from logging import DEBUG, FileHandler, getLogger
from twisted.internet.error import CannotListenError from twisted.internet.error import CannotListenError
from deluge.argparserbase import ArgParserBase
from deluge.common import run_profiled from deluge.common import run_profiled
from deluge.configmanager import get_config_dir from deluge.configmanager import get_config_dir
from deluge.ui.baseargparser import BaseArgParser from deluge.i18n import setup_mock_translation
from deluge.ui.translations_util import set_dummy_trans
def add_daemon_options(parser): def add_daemon_options(parser):
@ -78,10 +78,10 @@ def start_daemon(skip_start=False):
deluge.core.daemon.Daemon: A new daemon object deluge.core.daemon.Daemon: A new daemon object
""" """
set_dummy_trans(warn_msg=True) setup_mock_translation(warn_msg=True)
# Setup the argument parser # Setup the argument parser
parser = BaseArgParser() parser = ArgParserBase()
add_daemon_options(parser) add_daemon_options(parser)
options = parser.parse_args() options = parser.parse_args()

15
deluge/i18n/__init__.py Normal file
View file

@ -0,0 +1,15 @@
from .util import (
I18N_DOMAIN,
get_languages,
set_language,
setup_mock_translation,
setup_translation,
)
__all__ = [
'I18N_DOMAIN',
'set_language',
'get_languages',
'setup_translation',
'setup_mock_translation',
]

View file

@ -9,6 +9,12 @@ from __future__ import unicode_literals
# http://www.i18nguy.com/unicode/language-identifiers.html # http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
# Deferred translation
def _(message):
return message
# Languages we provide translations for, out of the box. # Languages we provide translations for, out of the box.
LANGUAGES = { LANGUAGES = {
'af': _('Afrikaans'), 'af': _('Afrikaans'),
@ -107,3 +113,5 @@ LANGUAGES = {
'zh-hant': _('Traditional Chinese'), 'zh-hant': _('Traditional Chinese'),
'zh_TW': _('Chinese (Taiwan)'), 'zh_TW': _('Chinese (Taiwan)'),
} }
del _

View file

@ -20,35 +20,19 @@ from six.moves import builtins
import deluge.common import deluge.common
from .languages import LANGUAGES
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.addHandler(
logging.NullHandler()
) # Silence: No handlers could be found for logger "deluge.util.lang"
I18N_DOMAIN = 'deluge' I18N_DOMAIN = 'deluge'
def set_dummy_trans(warn_msg=None):
def _func(*txt):
if warn_msg:
log.warning(
'"%s" has been marked for translation, but translation is unavailable.',
txt[0],
)
return txt[0]
builtins.__dict__['_'] = _func
builtins.__dict__['ngettext'] = builtins.__dict__['_n'] = _func
def get_translations_path(): def get_translations_path():
"""Get the absolute path to the directory containing translation files""" """Get the absolute path to the directory containing translation files"""
return deluge.common.resource_filename('deluge', 'i18n') return deluge.common.resource_filename('deluge', 'i18n')
def get_languages(): def get_languages():
from deluge.ui import languages # Import here so that gettext has been setup first
lang = [] lang = []
translations_path = get_translations_path() translations_path = get_translations_path()
@ -61,9 +45,9 @@ def get_languages():
for i, lang_code in enumerate(lang_dirs): for i, lang_code in enumerate(lang_dirs):
name = '%s (Language name missing)' % lang_code name = '%s (Language name missing)' % lang_code
if lang_code in languages.LANGUAGES: if lang_code in LANGUAGES:
name = languages.LANGUAGES[lang_code] name = LANGUAGES[lang_code]
lang.append([lang_code, name]) lang.append([lang_code, _(name)])
lang = sorted(lang, key=lambda l: l[1]) lang = sorted(lang, key=lambda l: l[1])
return lang return lang
@ -93,8 +77,21 @@ def set_language(lang):
log.warning('IOError when loading translations: %s', ex) log.warning('IOError when loading translations: %s', ex)
def setup_mock_translation(warn_msg=None):
def _func(*txt):
if warn_msg:
log.warning(
'"%s" has been marked for translation, but translation is unavailable.',
txt[0],
)
return txt[0]
builtins.__dict__['_'] = _func
builtins.__dict__['ngettext'] = builtins.__dict__['_n'] = _func
# Initialize gettext # Initialize gettext
def setup_translations(): def setup_translation():
translations_path = get_translations_path() translations_path = get_translations_path()
log.info('Setting up translations from %s', translations_path) log.info('Setting up translations from %s', translations_path)
@ -131,6 +128,6 @@ def setup_translations():
except Exception as ex: except Exception as ex:
log.error('Unable to initialize gettext/locale!') log.error('Unable to initialize gettext/locale!')
log.exception(ex) log.exception(ex)
set_dummy_trans() setup_mock_translation()
deluge.common.translate_size_units() deluge.common.translate_size_units()

View file

@ -23,7 +23,7 @@ import deluge.configmanager
import deluge.core.preferencesmanager import deluge.core.preferencesmanager
import deluge.log import deluge.log
from deluge.error import DelugeError from deluge.error import DelugeError
from deluge.ui.translations_util import setup_translations from deluge.i18n import setup_translation
# This sets log level to critical, so use log.critical() to debug while running unit tests # This sets log level to critical, so use log.critical() to debug while running unit tests
deluge.log.setup_logger('none') deluge.log.setup_logger('none')
@ -75,7 +75,7 @@ def add_watchdog(deferred, timeout=0.05, message=None):
# Initialize gettext # Initialize gettext
setup_translations() setup_translation()
class ReactorOverride(object): class ReactorOverride(object):

View file

@ -30,7 +30,7 @@ from deluge.common import (
is_url, is_url,
windows_check, windows_check,
) )
from deluge.ui.translations_util import setup_translations from deluge.i18n import setup_translation
from .common import get_test_data_file, set_tmp_config_dir from .common import get_test_data_file, set_tmp_config_dir
@ -38,7 +38,7 @@ from .common import get_test_data_file, set_tmp_config_dir
class CommonTestCase(unittest.TestCase): class CommonTestCase(unittest.TestCase):
def setUp(self): # NOQA def setUp(self): # NOQA
self.config_dir = set_tmp_config_dir() self.config_dir = set_tmp_config_dir()
setup_translations() setup_translation()
def tearDown(self): # NOQA def tearDown(self): # NOQA
pass pass

View file

@ -13,7 +13,7 @@ from twisted.trial import unittest
import deluge.component as component import deluge.component as component
from deluge.common import windows_check from deluge.common import windows_check
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.translations_util import setup_translations from deluge.i18n import setup_translation
from . import common from . import common
from .basetest import BaseTestCase from .basetest import BaseTestCase
@ -27,7 +27,7 @@ try:
except ImportError: except ImportError:
libs_available = False libs_available = False
setup_translations() setup_translation()
@pytest.mark.gtkui @pytest.mark.gtkui

View file

@ -15,7 +15,7 @@ from twisted.trial import unittest
import deluge.component as component import deluge.component as component
from deluge.configmanager import ConfigManager from deluge.configmanager import ConfigManager
from deluge.ui.translations_util import setup_translations from deluge.i18n import setup_translation
from . import common from . import common
from .basetest import BaseTestCase from .basetest import BaseTestCase
@ -36,7 +36,7 @@ except (ImportError, ValueError):
else: else:
libs_available = True libs_available = True
setup_translations() setup_translation()
@pytest.mark.gtkui @pytest.mark.gtkui

View file

@ -15,7 +15,7 @@ import os
import sys import sys
import deluge.common import deluge.common
from deluge.ui.baseargparser import BaseArgParser, DelugeTextHelpFormatter from deluge.argparserbase import ArgParserBase, DelugeTextHelpFormatter
from deluge.ui.ui import UI from deluge.ui.ui import UI
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -143,7 +143,7 @@ class Console(UI):
def start(self): def start(self):
if self.ui_args is None: if self.ui_args is None:
# Started directly by deluge-console script so must find the UI args manually # Started directly by deluge-console script so must find the UI args manually
options, remaining = BaseArgParser(common_help=False).parse_known_args() options, remaining = ArgParserBase(common_help=False).parse_known_args()
self.ui_args = remaining self.ui_args = remaining
i = self.console_parser.find_subcommand(args=self.ui_args) i = self.console_parser.find_subcommand(args=self.ui_args)

View file

@ -13,6 +13,7 @@ import logging
from deluge.common import is_ip from deluge.common import is_ip
from deluge.decorators import overrides from deluge.decorators import overrides
from deluge.i18n import get_languages
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.common import DISK_CACHE_KEYS from deluge.ui.common import DISK_CACHE_KEYS
from deluge.ui.console.widgets import BaseInputPane, BaseWindow from deluge.ui.console.widgets import BaseInputPane, BaseWindow
@ -192,7 +193,6 @@ class InterfacePane(BasePreferencePane):
_('Move selection when moving torrents in the queue'), _('Move selection when moving torrents in the queue'),
console_config['torrentview']['move_selection'], console_config['torrentview']['move_selection'],
) )
from deluge.ui.translations_util import get_languages
langs = get_languages() langs = get_languages()
langs.insert(0, ('', 'System Default')) langs.insert(0, ('', 'System Default'))

View file

@ -13,11 +13,11 @@ import copy
import logging import logging
import deluge.common import deluge.common
from deluge.i18n import setup_translation
from deluge.ui.common import TORRENT_DATA_FIELD from deluge.ui.common import TORRENT_DATA_FIELD
from deluge.ui.console.utils import format_utils from deluge.ui.console.utils import format_utils
from deluge.ui.translations_util import setup_translations
setup_translations() setup_translation()
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -47,11 +47,11 @@ from deluge.common import (
) )
from deluge.configmanager import ConfigManager, get_config_dir from deluge.configmanager import ConfigManager, get_config_dir
from deluge.error import DaemonRunningError from deluge.error import DaemonRunningError
from deluge.i18n import I18N_DOMAIN, set_language, setup_translation
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.hostlist import LOCALHOST from deluge.ui.hostlist import LOCALHOST
from deluge.ui.sessionproxy import SessionProxy from deluge.ui.sessionproxy import SessionProxy
from deluge.ui.tracker_icons import TrackerIcons from deluge.ui.tracker_icons import TrackerIcons
from deluge.ui.translations_util import I18N_DOMAIN, set_language, setup_translations
# isort:imports-localfolder # isort:imports-localfolder
from .addtorrentdialog import AddTorrentDialog from .addtorrentdialog import AddTorrentDialog
@ -146,7 +146,7 @@ def windowing(like):
class GtkUI(object): class GtkUI(object):
def __init__(self, args): def __init__(self, args):
# Setup gtkbuilder/glade translation # Setup gtkbuilder/glade translation
setup_translations() setup_translation()
Builder().set_translation_domain(I18N_DOMAIN) Builder().set_translation_domain(I18N_DOMAIN)
# Setup signals # Setup signals

View file

@ -22,9 +22,9 @@ import deluge.common
import deluge.component as component import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir from deluge.configmanager import ConfigManager, get_config_dir
from deluge.error import AuthManagerError, NotAuthorizedError from deluge.error import AuthManagerError, NotAuthorizedError
from deluge.i18n import get_languages
from deluge.ui.client import client from deluge.ui.client import client
from deluge.ui.common import DISK_CACHE_KEYS, PREFS_CATOG_TRANS from deluge.ui.common import DISK_CACHE_KEYS, PREFS_CATOG_TRANS
from deluge.ui.translations_util import get_languages
from .common import associate_magnet_links, get_clipboard_text, get_deluge_icon from .common import associate_magnet_links, get_clipboard_text, get_deluge_icon
from .dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog from .dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog

View file

@ -14,8 +14,8 @@ import logging
import deluge.common import deluge.common
import deluge.configmanager import deluge.configmanager
import deluge.log import deluge.log
from deluge.ui.baseargparser import BaseArgParser from deluge.argparserbase import ArgParserBase
from deluge.ui.translations_util import setup_translations from deluge.i18n import setup_translation
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -38,8 +38,8 @@ class UI(object):
def __init__(self, name, **kwargs): def __init__(self, name, **kwargs):
self.__name = name self.__name = name
self.ui_args = kwargs.pop('ui_args', None) self.ui_args = kwargs.pop('ui_args', None)
setup_translations() setup_translation()
self.__parser = BaseArgParser(**kwargs) self.__parser = ArgParserBase(**kwargs)
def parse_args(self, parser, args=None): def parse_args(self, parser, args=None):
options = parser.parse_args(args) options = parser.parse_args(args)

View file

@ -23,8 +23,8 @@ import pkg_resources
import deluge.common import deluge.common
import deluge.configmanager import deluge.configmanager
from deluge.ui.baseargparser import BaseArgParser from deluge.argparserbase import ArgParserBase
from deluge.ui.translations_util import setup_translations from deluge.i18n import setup_translation
DEFAULT_PREFS = {'default_ui': 'gtk'} DEFAULT_PREFS = {'default_ui': 'gtk'}
@ -33,7 +33,7 @@ AMBIGUOUS_CMD_ARGS = ('-h', '--help', '-v', '-V', '--version')
def start_ui(): def start_ui():
"""Entry point for ui script""" """Entry point for ui script"""
setup_translations() setup_translation()
# Get the registered UI entry points # Get the registered UI entry points
ui_entrypoints = {} ui_entrypoints = {}
@ -59,7 +59,7 @@ def start_ui():
return _parser return _parser
# Setup parser with Common Options and add UI Options group. # Setup parser with Common Options and add UI Options group.
parser = add_ui_options_group(BaseArgParser()) parser = add_ui_options_group(ArgParserBase())
# Parse and handle common/process group options # Parse and handle common/process group options
options = parser.parse_known_ui_args(sys.argv, withhold=AMBIGUOUS_CMD_ARGS) options = parser.parse_known_ui_args(sys.argv, withhold=AMBIGUOUS_CMD_ARGS)
@ -81,7 +81,7 @@ def start_ui():
# We have parsed and got the config dir needed to get the default UI # We have parsed and got the config dir needed to get the default UI
# Now create a parser for choosing the UI. We reuse the ui option group for # Now create a parser for choosing the UI. We reuse the ui option group for
# parsing to succeed and the text displayed to user, but result is not used. # parsing to succeed and the text displayed to user, but result is not used.
parser = add_ui_options_group(BaseArgParser(common_help=True)) parser = add_ui_options_group(ArgParserBase(common_help=True))
# Create subparser for each registered UI. Empty title is used to remove unwanted positional text. # Create subparser for each registered UI. Empty title is used to remove unwanted positional text.
subparsers = parser.add_subparsers( subparsers = parser.add_subparsers(

View file

@ -26,12 +26,12 @@ from deluge import component, httpdownloader
from deluge.common import AUTH_LEVEL_DEFAULT, get_magnet_info, is_magnet from deluge.common import AUTH_LEVEL_DEFAULT, get_magnet_info, is_magnet
from deluge.configmanager import get_config_dir from deluge.configmanager import get_config_dir
from deluge.error import NotAuthorizedError from deluge.error import NotAuthorizedError
from deluge.i18n import get_languages
from deluge.ui.client import Client, client from deluge.ui.client import Client, client
from deluge.ui.common import FileTree2, TorrentInfo from deluge.ui.common import FileTree2, TorrentInfo
from deluge.ui.coreconfig import CoreConfig from deluge.ui.coreconfig import CoreConfig
from deluge.ui.hostlist import HostList from deluge.ui.hostlist import HostList
from deluge.ui.sessionproxy import SessionProxy from deluge.ui.sessionproxy import SessionProxy
from deluge.ui.translations_util import get_languages
from deluge.ui.web.common import _ from deluge.ui.web.common import _
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -25,8 +25,8 @@ from deluge import common, component, configmanager
from deluge.common import is_ipv6 from deluge.common import is_ipv6
from deluge.core.rpcserver import check_ssl_keys from deluge.core.rpcserver import check_ssl_keys
from deluge.crypto_utils import get_context_factory from deluge.crypto_utils import get_context_factory
from deluge.i18n import set_language, setup_translation
from deluge.ui.tracker_icons import TrackerIcons from deluge.ui.tracker_icons import TrackerIcons
from deluge.ui.translations_util import set_language, setup_translations
from deluge.ui.web.auth import Auth from deluge.ui.web.auth import Auth
from deluge.ui.web.common import Template from deluge.ui.web.common import Template
from deluge.ui.web.json_api import JSON, WebApi, WebUtils from deluge.ui.web.json_api import JSON, WebApi, WebUtils
@ -687,7 +687,7 @@ class DelugeWeb(component.Component):
# Strip away slashes and serve on the base path as well as root path # Strip away slashes and serve on the base path as well as root path
self.top_level.putChild(self.base.strip('/'), self.top_level) self.top_level.putChild(self.base.strip('/'), self.top_level)
setup_translations() setup_translation()
# Remove twisted version number from 'server' http-header for security reasons # Remove twisted version number from 'server' http-header for security reasons
server.version = 'TwistedWeb' server.version = 'TwistedWeb'