Skip to content

Commit

Permalink
Initial localization support
Browse files Browse the repository at this point in the history
This commit introduces the initial changes necessary for allowing,
our code and actors to be localizable with gettext.

Magic builtins _() and P_() have been introduced with inspiration
taken from the DNF implementation to solve this.

Usage examples:
    Localization without need of pluralization:
        localized_string = _('localized')
    Localization with the need of pluralization:
        localized_string = P_('%d item', '%d items', 2)

Signed-off-by: Vinzenz Feenstra <[email protected]>
  • Loading branch information
vinzenz authored and pirat89 committed Jan 9, 2019
1 parent 72d33fc commit 8ebf682
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 21 deletions.
8 changes: 8 additions & 0 deletions leapp/actors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from leapp.exceptions import MissingActorAttributeError, WrongAttributeTypeError
from leapp.models import Model
from leapp.tags import Tag
from leapp.utils.i18n import install_translation_for_actor
from leapp.utils.meta import get_flattened_subclasses
from leapp.models.error_severity import ErrorSeverity

Expand Down Expand Up @@ -52,7 +53,14 @@ class Actor(object):
Dialogs that are added to this list allow for persisting answers the user has given in the answer file storage.
"""

text_domain = None
"""
Using text domain allows to override the default gettext text domain, for custom localization support.
The default locale installation location is used which usually is /usr/share/locale
"""

def __init__(self, messaging=None, logger=None):
install_translation_for_actor(type(self))
self._messaging = messaging
self.log = (logger or logging.getLogger('leapp.actors')).getChild(self.name)
""" A configured logger instance for the current actor. """
Expand Down
1 change: 1 addition & 0 deletions leapp/cli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import leapp.utils.i18n
from leapp.cli import main

main()
31 changes: 30 additions & 1 deletion leapp/compat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import locale
import sys
import gettext


__all__ = ('string_types', 'IS_PYTHON2', 'IS_PYTHON3', 'httplib', 'unicode_type', 'raise_with_traceback')
Expand All @@ -16,13 +18,40 @@
string_types = (str, globals()['__builtins__']['unicode'])
unicode_type = string_types[1]
from leapp.compatpy2only import raise_with_traceback
builtins_dict = globals()['__builtins__']

def gettext_setup(t):
def us(u):
if isinstance(u, globals()['__builtins__']['unicode']):
return u
return unicode_type(u)

def singular(msg):
return t.ugettext(us(msg))

def plural(msg1, msg2, n):
return t.ungettext(us(msg1), us(msg2), n)

return singular, plural

def setlocale(category, loc=None):
locale.setlocale(category, loc.encode('utf-8') if loc else None)

# Python 3 code
else:

import http.client as httplib
import builtins
string_types = (str,)
unicode_type = str
builtins_dict = builtins.__dict__

def gettext_setup(t):
singular = t.gettext
plural = t.ngettext
return singular, plural

def setlocale(category, loc=None):
locale.setlocale(category, loc)

def raise_with_traceback(exc, tb):
"""
Expand Down
Empty file added leapp/dialogs/message.py
Empty file.
2 changes: 1 addition & 1 deletion leapp/repository/actor_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def injected_context(self):
leapp.libraries.actor.__dict__.pop(symbol)

# Remove all modules from the sys.modules dict or restore from backup if it was there
for name, _ in to_add:
for name, unused in to_add:
if name in backup:
sys.modules[name] = backup[name]
else:
Expand Down
18 changes: 9 additions & 9 deletions leapp/repository/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def scan_topics(repo, path, repo_path):
:param repo_path: path to the repository
:type repo_path: str
"""
for root, _, files in os.walk(path):
for root, unused, files in os.walk(path):
for module in files:
_, ext = os.path.splitext(module)
unused, ext = os.path.splitext(module)
if ext == '.py':
path = os.path.join(root, module)
repo.add(DefinitionKind.TOPIC, os.path.relpath(path, repo_path))
Expand All @@ -126,7 +126,7 @@ def scan_actors(repo, path, repo_path):
:param repo_path: path to the repository
:type repo_path: str
"""
for root, _, files in os.walk(path):
for root, unused, files in os.walk(path):
for module in files:
if module == 'actor.py':
rel_path = os.path.relpath(root, repo_path)
Expand All @@ -144,9 +144,9 @@ def scan_tags(repo, path, repo_path):
:param repo_path: path to the repository
:type repo_path: str
"""
for root, _, files in os.walk(path):
for root, unused, files in os.walk(path):
for module in files:
_, ext = os.path.splitext(module)
unused, ext = os.path.splitext(module)
if ext == '.py':
path = os.path.join(root, module)
repo.add(DefinitionKind.TAG, os.path.relpath(path, repo_path))
Expand All @@ -163,9 +163,9 @@ def scan_models(repo, path, repo_path):
:param repo_path: path to the repository
:type repo_path: str
"""
for root, _, files in os.walk(path):
for root, unused, files in os.walk(path):
for module in files:
_, ext = os.path.splitext(module)
unused, ext = os.path.splitext(module)
if ext == '.py':
path = os.path.join(root, module)
repo.add(DefinitionKind.MODEL, os.path.relpath(path, repo_path))
Expand All @@ -182,9 +182,9 @@ def scan_workflows(repo, path, repo_path):
:param repo_path: path to the repository
:type repo_path: str
"""
for root, _, files in os.walk(path):
for root, unused, files in os.walk(path):
for module in files:
_, ext = os.path.splitext(module)
unused, ext = os.path.splitext(module)
if ext == '.py':
path = os.path.join(root, module)
repo.add(DefinitionKind.WORKFLOW, os.path.relpath(path, repo_path))
Expand Down
15 changes: 7 additions & 8 deletions leapp/snactor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pkgutil
import socket

from leapp.utils.i18n import _
from leapp.snactor import commands
from leapp.snactor.commands import workflow
from leapp.snactor.commands import messages
Expand All @@ -10,10 +11,8 @@
from leapp.utils.repository import find_repository_basedir
from leapp import VERSION

SHORT_HELP = "snactor is a development and repository management tool for Leapp."
LONG_HELP = """
Snactor is designed to get quickly started with leapp actor development.
"""
SHORT_HELP = _("snactor is a development and repository management tool for Leapp.")
LONG_HELP = _("""Snactor is designed to get quickly started with leapp actor development.""")


def load_commands():
Expand All @@ -38,9 +37,9 @@ def _load_commands_from(path):


@command('', help=LONG_HELP)
@command_opt('debug', is_flag=True, help='Enables debug logging', inherit=True)
@command_opt('config', help='Allows to override the leapp.conf location', inherit=True)
@command_opt('logger-config', help='Allows to override the logger.conf location', inherit=True)
@command_opt('debug', is_flag=True, help=_('Enables debug logging'), inherit=True)
@command_opt('config', help=_('Allows to override the leapp.conf location'), inherit=True)
@command_opt('logger-config', help=_('Allows to override the logger.conf location'), inherit=True)
def cli(args):
if args.logger_config and os.path.isfile(args.logger_config):
os.environ['LEAPP_LOGGER_CONFIG'] = args.logger_config
Expand All @@ -65,4 +64,4 @@ def cli(args):
def main():
os.environ['LEAPP_HOSTNAME'] = socket.getfqdn()
load_commands()
cli.command.execute(version='snactor version {}'.format(VERSION))
cli.command.execute(version=_('snactor version {}').format(VERSION))
3 changes: 1 addition & 2 deletions leapp/snactor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import functools
import itertools
import os
import six


def print_section(data, section, pivot):
Expand All @@ -26,7 +25,7 @@ def format_file_path(pivot, path):

def get_candidate_files(start='.'):
"Find all .py files in a directory tree"
for root, _, files in os.walk(start):
for root, unused, files in os.walk(start):
for f in files:
if not f.endswith('py'):
continue
Expand Down
30 changes: 30 additions & 0 deletions leapp/utils/i18n.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import leapp.compat

import locale
import os
import logging


def setup_locale():
try:
leapp.compat.setlocale(locale.LC_ALL)
except locale.Error:
logging.getLogger('leapp.i18n').error('Failed to set locale, defaulting to C', exc_info=True)
os.environ['LC_ALL'] = 'C'
leapp.compat.setlocale(locale.LC_ALL, 'C')


def translation(domain):
setup_locale()
return leapp.compat.gettext_setup(
leapp.compat.gettext.translation(domain, fallback=True))


def install_translation_for_actor(actor):
if actor.text_domain:
globals()['__builtins__']['_'], globals()['__builtins__']['P_'] = translation(actor.text_domain)


_, P_ = translation(os.environ.get('LEAPP_DEFAULT_TEXTDOMAIN', 'leapp'))

globals()['__builtins__']['_'], globals()['__builtins__']['P_'] = _, P_

0 comments on commit 8ebf682

Please sign in to comment.