diff --git a/lib/yunohost/app.py b/lib/yunohost/app.py index 33188e27..1cb2e609 100644 --- a/lib/yunohost/app.py +++ b/lib/yunohost/app.py @@ -33,6 +33,7 @@ import time import re import socket import urlparse +import errno from moulinette.helpers import win_msg, random_password, is_true, validate from moulinette.core import MoulinetteError @@ -55,7 +56,7 @@ def app_listlists(): if '.json' in filename: list_list.append(filename[:len(filename)-5]) except OSError: - raise MoulinetteError(1, _("No list found")) + raise MoulinetteError(1, m18n.n('no_list_found')) return { 'Lists' : list_list } @@ -77,12 +78,14 @@ def app_fetchlist(url=None, name=None): url = 'http://app.yunohost.org/list.json' name = 'yunohost' else: - if name is None: raise MoulinetteError(22, _("You must indicate a name for your custom list")) + if name is None: + raise MoulinetteError(errno.EINVAL, + m18n.n('custom_list_name_required')) list_file = '%s/%s.json' % (repo_path, name) if os.system('wget "%s" -O "%s.tmp"' % (url, list_file)) != 0: os.remove('%s.tmp' % list_file) - raise MoulinetteError(1, _("List server connection failed")) + raise MoulinetteError(errno.EBADR, m18n.n('list_retrieve_error')) # Rename fetched temp list os.rename('%s.tmp' % list_file, list_file) @@ -90,7 +93,7 @@ def app_fetchlist(url=None, name=None): os.system("touch /etc/cron.d/yunohost-applist-%s" % name) os.system("echo '00 00 * * * root yunohost app fetchlist -u %s -n %s --no-ldap > /dev/null 2>&1' >/etc/cron.d/yunohost-applist-%s" % (url, name, name)) - msignals.display(_("List successfully fetched"), 'success') + msignals.display(m18n.n('list_fetched'), 'success') def app_removelist(name): @@ -105,9 +108,9 @@ def app_removelist(name): os.remove('%s/%s.json' % (repo_path, name)) os.remove("/etc/cron.d/yunohost-applist-%s" % name) except OSError: - raise MoulinetteError(22, _("Unknown list")) + raise MoulinetteError(errno.ENOENT, m18n.n('unknown_list')) - msignals.display(_("List successfully removed"), 'success') + msignals.display(m18n.n('list_removed'), 'success') def app_list(offset=None, limit=None, filter=None, raw=False): @@ -268,7 +271,7 @@ def app_upgrade(auth, app, url=None, file=None): try: app_list() except MoulinetteError: - raise MoulinetteError(1, _("No app to upgrade")) + raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade')) upgraded_apps = [] @@ -281,7 +284,8 @@ def app_upgrade(auth, app, url=None, file=None): for app_id in app: installed = _is_installed(app_id) if not installed: - raise MoulinetteError(1, _("%s is not installed") % app_id) + raise MoulinetteError(errno.ENOPKG, + m18n.n('app_not_installed') % app_id) if app_id in upgraded_apps: continue @@ -299,7 +303,8 @@ def app_upgrade(auth, app, url=None, file=None): elif url: manifest = _fetch_app_from_git(url) elif 'lastUpdate' not in new_app_dict or 'git' not in new_app_dict: - raise MoulinetteError(22, _("%s is a custom app, please provide an URL manually in order to upgrade it") % app_id) + raise MoulinetteError(errno.EDESTADDRREQ, + m18n.n('custom_app_url_required') % app_id) elif (new_app_dict['lastUpdate'] > current_app_dict['lastUpdate']) \ or ('update_time' not in current_app_dict['settings'] \ and (new_app_dict['lastUpdate'] > current_app_dict['settings']['install_time'])) \ @@ -311,7 +316,8 @@ def app_upgrade(auth, app, url=None, file=None): # Check min version if 'min_version' in manifest and __version__ < manifest['min_version']: - raise MoulinetteError(1, _("%s requires a more recent version of the moulinette") % app_id) + raise MoulinetteError(errno.EPERM, + m18n.n('app_recent_version_required') % app_id) app_setting_path = apps_setting_path +'/'+ app_id @@ -355,12 +361,12 @@ def app_upgrade(auth, app, url=None, file=None): # So much win upgraded_apps.append(app_id) - msignals.display(_("%s upgraded successfully") % app_id, 'success') + msignals.display(m18n.n('app_upgraded') % app_id, 'success') if not upgraded_apps: - raise MoulinetteError(1, _("No app to upgrade")) + raise MoulinetteError(errno.ENODATA, m18n.n('no_app_upgrade')) - msignals.display(_("Upgrade complete"), 'success') + msignals.display(m18n.n('upgrade_complete'), 'success') def app_install(auth, app, label=None, args=None): @@ -386,19 +392,21 @@ def app_install(auth, app, label=None, args=None): # Check ID if 'id' not in manifest or '__' in manifest['id']: - raise MoulinetteError(22, _("App id is invalid")) + raise MoulinetteError(errno.EINVAL, m18n.n('app_id_invalid')) app_id = manifest['id'] # Check min version if 'min_version' in manifest and __version__ < manifest['min_version']: - raise MoulinetteError(1, _("%s requires a more recent version of the moulinette") % app_id) + raise MoulinetteError(errno.EPERM, + m18n.n('app_recent_version_required') % app_id) # Check if app can be forked instance_number = _installed_instance_number(app_id, last=True) + 1 if instance_number > 1 : if 'multi_instance' not in manifest or not is_true(manifest['multi_instance']): - raise MoulinetteError(1, _("App is already installed")) + raise MoulinetteError(errno.EEXIST, + m18n.n('app_already_installed') % app_id) app_id_forked = app_id + '__' + str(instance_number) @@ -467,18 +475,18 @@ def app_install(auth, app, label=None, args=None): os.system('chown -R root: %s' % app_setting_path) os.system('chown -R admin: %s/scripts' % app_setting_path) app_ssowatconf(auth) - msignals.display(_("Installation complete"), 'success') + msignals.display(m18n.n('installation_complete'), 'success') else: #TODO: display script fail messages hook_remove(app_id) shutil.rmtree(app_setting_path) shutil.rmtree(app_tmp_folder) - raise MoulinetteError(1, _("Installation failed")) + raise MoulinetteError(errno.EIO, m18n.n('installation_failed')) except KeyboardInterrupt, EOFError: hook_remove(app_id) shutil.rmtree(app_setting_path) shutil.rmtree(app_tmp_folder) - raise MoulinetteError(125, _("Interrupted")) + raise MoulinetteError(errno.EINTR, m18n.g('operation_interrupted')) def app_remove(app): @@ -492,7 +500,7 @@ def app_remove(app): from yunohost.hook import hook_exec, hook_remove if not _is_installed(app): - raise MoulinetteError(22, _("App is not installed")) + raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed') % app) app_setting_path = apps_setting_path + app @@ -512,7 +520,7 @@ def app_remove(app): shutil.rmtree('/tmp/yunohost_remove') hook_remove(app) app_ssowatconf() - msignals.display(_("App removed: %s") % app, 'success') + msignals.display(m18n.n('app_removed') % app, 'success') def app_addaccess(auth, apps, users): @@ -536,7 +544,8 @@ def app_addaccess(auth, apps, users): for app in apps: if not _is_installed(app): - raise MoulinetteError(22, _("App is not installed")) + raise MoulinetteError(errno.EINVAL, + m18n.n('app_not_installed') % app) with open(apps_setting_path + app +'/settings.yml') as f: app_settings = yaml.load(f) @@ -589,7 +598,8 @@ def app_removeaccess(auth, apps, users): new_users = '' if not _is_installed(app): - raise MoulinetteError(22, _("App is not installed")) + raise MoulinetteError(errno.EINVAL, + m18n.n('app_not_installed') % app) with open(apps_setting_path + app +'/settings.yml') as f: app_settings = yaml.load(f) @@ -631,7 +641,8 @@ def app_clearaccess(auth, apps): for app in apps: if not _is_installed(app): - raise MoulinetteError(22, _("App is not installed")) + raise MoulinetteError(errno.EINVAL, + m18n.n('app_not_installed') % app) with open(apps_setting_path + app +'/settings.yml') as f: app_settings = yaml.load(f) @@ -654,7 +665,7 @@ def app_makedefault(app, domain=None): """ if not _is_installed(app): - raise MoulinetteError(22, _("App is not installed")) + raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed') % app) with open(apps_setting_path + app +'/settings.yml') as f: app_settings = yaml.load(f) @@ -665,10 +676,11 @@ def app_makedefault(app, domain=None): if domain is None: domain = app_domain elif domain not in domain_list()['Domains']: - raise MoulinetteError(22, _("Domain doesn't exists")) + raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) if '/' in app_map(raw=True)[domain]: - raise MoulinetteError(1, _("An app is already installed on this location")) + raise MoulinetteError(errno.EEXIST, + m18n.n('app_location_already_used')) try: with open('/etc/ssowat/conf.json.persistent') as json_conf: @@ -686,7 +698,7 @@ def app_makedefault(app, domain=None): os.system('chmod 644 /etc/ssowat/conf.json.persistent') - win_msg('SSOwat persistent configuration has been updated') + msignals.display(m18n.n('ssowat_conf_updated'), 'success') @@ -780,10 +792,10 @@ def app_checkport(port): s.connect(("localhost", int(port))) s.close() except socket.error: - msignals.display(_("Port available: %s") % str(port), 'success') + msignals.display(m18n.n('port_available') % int(port), 'success') else: - raise MoulinetteError(22, _("Port not available: %s") % str(port)) - + raise MoulinetteError(errno.EINVAL, + m18n.n('port_unavailable') % int(port)) def app_checkurl(auth, url, app=None): @@ -815,14 +827,15 @@ def app_checkurl(auth, url, app=None): validate(r'^([a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)(\.[a-zA-Z0-9]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)*(\.[a-zA-Z]{1}([a-zA-Z0-9\-]*[a-zA-Z0-9])*)$', domain) if domain not in domain_list(auth)['domains']: - raise MoulinetteError(22, _("Domain doesn't exists")) + raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) if domain in apps_map: if path in apps_map[domain]: - raise MoulinetteError(1, _("An app is already installed on this location")) + raise MoulinetteError(errno.EINVAL, m18n.n('app_location_already_used')) for app_path, v in apps_map[domain].items(): if app_path in path and app_path.count('/') < path.count('/'): - raise MoulinetteError(1, _("Unable to install app at this location")) + raise MoulinetteError(errno.EPERM, + m18n.n('app_location_install_failed')) if app is not None: app_setting(app, 'domain', value=domain) @@ -852,13 +865,13 @@ def app_initdb(user, password=None, db=None, sql=None): mysql_root_pwd = open('/etc/yunohost/mysql').read().rstrip() mysql_command = 'mysql -u root -p%s -e "CREATE DATABASE %s ; GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@localhost IDENTIFIED BY \'%s\';"' % (mysql_root_pwd, db, db, user, password) if os.system(mysql_command) != 0: - raise MoulinetteError(1, _("MySQL DB creation failed")) + raise MoulinetteError(errno.EIO, m18n.n('mysql_db_creation_failed')) if sql is not None: if os.system('mysql -u %s -p%s %s < %s' % (user, password, db, sql)) != 0: - raise MoulinetteError(1, _("MySQL DB init failed")) + raise MoulinetteError(errno.EIO, m18n.n('mysql_db_init_failed')) if not return_pwd: - msignals.display(_("Database initiliazed"), 'success') + msignals.display(m18n.n('mysql_db_initialized'), 'success') def app_ssowatconf(auth): @@ -951,7 +964,7 @@ def app_ssowatconf(auth): with open('/etc/ssowat/conf.json', 'w+') as f: json.dump(conf_dict, f, sort_keys=True, indent=4) - msignals.display(_('SSOwat configuration generated'), 'success') + msignals.display(m18n.n('ssowat_conf_generated'), 'success') def _extract_app_from_file(path, remove=False): @@ -968,7 +981,7 @@ def _extract_app_from_file(path, remove=False): """ global app_tmp_folder - print(_('Extracting...')) + msignals.display(m18n.n('extracting')) if os.path.exists(app_tmp_folder): shutil.rmtree(app_tmp_folder) os.makedirs(app_tmp_folder) @@ -988,7 +1001,7 @@ def _extract_app_from_file(path, remove=False): extract_result = 1 if extract_result != 0: - raise MoulinetteError(22, _("Invalid install file")) + raise MoulinetteError(errno.EINVAL, m18n.n('app_extraction_failed')) try: if len(os.listdir(app_tmp_folder)) == 1: @@ -998,9 +1011,9 @@ def _extract_app_from_file(path, remove=False): manifest = json.loads(str(json_manifest.read())) manifest['lastUpdate'] = int(time.time()) except IOError: - raise MoulinetteError(1, _("Invalid App file")) + raise MoulinetteError(errno.EIO, m18n.n('app_install_files_invalid')) - print(_('OK')) + msignals.display(m18n.n('done')) return manifest @@ -1018,7 +1031,7 @@ def _fetch_app_from_git(app): """ global app_tmp_folder - print(_('Downloading...')) + msignals.display(m18n.n('downloading')) if ('@' in app) or ('http://' in app) or ('https://' in app): if "github.com" in app: @@ -1036,7 +1049,7 @@ def _fetch_app_from_git(app): manifest = json.loads(str(json_manifest.read())) manifest['lastUpdate'] = int(time.time()) except IOError: - raise MoulinetteError(1, _("Invalid App manifest")) + raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid')) else: app_dict = app_list(raw=True) @@ -1046,7 +1059,7 @@ def _fetch_app_from_git(app): app_info['manifest']['lastUpdate'] = app_info['lastUpdate'] manifest = app_info['manifest'] else: - raise MoulinetteError(22, _("App doesn't exists")) + raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown')) if "github.com" in app_info['git']['url']: url = app_info['git']['url'].replace("git@github.com:", "https://github.com/") @@ -1063,9 +1076,9 @@ def _fetch_app_from_git(app): git_result_2 = os.system('cd %s && git reset --hard %s' % (app_tmp_folder, str(app_info['git']['revision']))) if not git_result == git_result_2 == 0: - raise MoulinetteError(22, _("Sources fetching failed")) + raise MoulinetteError(errno.EIO, m18n.n('app_sources_fetch_failed')) - print(_('OK')) + msignals.display(m18n.n('done')) return manifest @@ -1134,4 +1147,3 @@ def _is_installed(app): continue return False - diff --git a/lib/yunohost/domain.py b/lib/yunohost/domain.py index d9dd0626..5faa37ac 100644 --- a/lib/yunohost/domain.py +++ b/lib/yunohost/domain.py @@ -30,6 +30,7 @@ import re import shutil import json import yaml +import errno from urllib import urlopen from moulinette.core import MoulinetteError @@ -89,7 +90,7 @@ def domain_add(auth, domains, main=False, dyndns=False): # DynDNS domain if dyndns: if len(domain.split('.')) < 3: - raise MoulinetteError(22, _("Invalid domain '%s' for DynDNS" % domain)) + raise MoulinetteError(errno.EINVAL, m18n.n('domain_dyndns_invalid')) import requests from yunohost.dyndns import dyndns_subscribe @@ -98,10 +99,12 @@ def domain_add(auth, domains, main=False, dyndns=False): dyndomain = '.'.join(domain.split('.')[1:]) if dyndomain in dyndomains: if os.path.exists('/etc/cron.d/yunohost-dyndns'): - raise MoulinetteError(22, _("You already have a DynDNS domain")) + raise MoulinetteError(errno.EPERM, + m18n.n('domain_dyndns_already_subscribed')) dyndns_subscribe(domain=domain) else: - raise MoulinetteError(22, _("Unknown DynDNS domain '%s'" % dyndomain)) + raise MoulinetteError(errno.EINVAL, + m18n.n('domain_dyndns_root_unknown')) # Commands ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA' @@ -131,12 +134,13 @@ def domain_add(auth, domains, main=False, dyndns=False): for command in command_list: if os.system(command) != 0: - raise MoulinetteError(17, _("An error occurred during certificate generation")) + raise MoulinetteError(errno.EIO, + m18n.n('domain_cert_gen_failed')) try: auth.validate_uniqueness({ 'virtualdomain': domain }) except MoulinetteError: - raise MoulinetteError(17, _("Domain already created")) + raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists')) attr_dict['virtualdomain'] = domain @@ -169,7 +173,8 @@ def domain_add(auth, domains, main=False, dyndns=False): os.system('chown bind /var/lib/bind/%s.zone' % domain) else: - raise MoulinetteError(17, _("Zone file already exists for %s") % domain) + raise MoulinetteError(errno.EEXIST, + m18n.n('domain_zone_exists')) conf_lines = [ 'zone "%s" {' % domain, @@ -228,12 +233,12 @@ def domain_add(auth, domains, main=False, dyndns=False): result.append(domain) continue else: - raise MoulinetteError(169, _("An error occurred during domain creation")) + raise MoulinetteError(errno.EIO, m18n.n('domain_creation_failed')) os.system('yunohost app ssowatconf > /dev/null 2>&1') - msignals.display(_("Domain(s) successfully created."), 'success') + msignals.display(m18n.n('domain_created'), 'success') return { 'domains': result } @@ -253,7 +258,7 @@ def domain_remove(auth, domains): for domain in domains: if domain not in domains_list: - raise MoulinetteError(22, _("Unknown domain '%s'") % domain) + raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) # Check if apps are installed on the domain for app in os.listdir('/etc/yunohost/apps/'): @@ -264,7 +269,8 @@ def domain_remove(auth, domains): continue else: if app_domain == domain: - raise MoulinetteError(1, _("One or more apps are installed on this domain, please uninstall them before proceed to domain removal")) + raise MoulinetteError(errno.EPERM, + m18n.n('domain_uninstall_app_first')) if auth.remove('virtualdomain=' + domain + ',ou=domains'): try: @@ -291,13 +297,12 @@ def domain_remove(auth, domains): result.append(domain) continue else: - raise MoulinetteError(169, _("An error occurred during domain deletion")) + raise MoulinetteError(errno.EIO, m18n.n('domain_deletion_failed')) os.system('yunohost app ssowatconf > /dev/null 2>&1') os.system('service nginx reload') os.system('service bind9 reload') os.system('service metronome restart') - msignals.display(_("Domain(s) successfully deleted."), 'success') + msignals.display(m18n.n('domain_deleted'), 'success') return { 'domains': result } - diff --git a/lib/yunohost/dyndns.py b/lib/yunohost/dyndns.py index 5525a8e3..08377753 100644 --- a/lib/yunohost/dyndns.py +++ b/lib/yunohost/dyndns.py @@ -29,6 +29,7 @@ import requests import json import glob import base64 +import errno from moulinette.core import MoulinetteError @@ -49,7 +50,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None # Verify if domain is available if requests.get('http://%s/test/%s' % (subscribe_host, domain)).status_code != 200: - raise MoulinetteError(17, _("DynDNS domain is already taken")) + raise MoulinetteError(errno.EEXIST, m18n.n('dyndns_unavailable')) if key is None: if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0: @@ -67,9 +68,10 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None if r.status_code != 201: try: error = json.loads(r.text)['error'] except: error = "Server error" - raise MoulinetteError(1, _("An error occurred during DynDNS registration: %s") % error) + raise MoulinetteError(errno.EPERM, + m18n.n('dyndns_registration_failed') % error) - msignals.display(_("Subscribed to DynDNS."), 'success') + msignals.display(m18n.n('dyndns_registered'), 'success') dyndns_installcron() @@ -134,12 +136,13 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, ip=Non else: private_key_file = key if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % private_key_file) == 0: - msignals.display(_("IP successfully updated."), 'success') + msignals.display(m18n.n('dyndns_ip_updated'), 'success') with open('/etc/yunohost/dyndns/old_ip', 'w') as f: f.write(new_ip) else: os.system('rm /etc/yunohost/dyndns/old_ip > /dev/null 2>&1') - raise MoulinetteError(1, _("An error occurred during DynDNS update")) + raise MoulinetteError(errno.EPERM, + m18n.n('dyndns_ip_update_failed')) def dyndns_installcron(): @@ -151,7 +154,7 @@ def dyndns_installcron(): with open('/etc/cron.d/yunohost-dyndns', 'w+') as f: f.write('*/2 * * * * root yunohost dyndns update >> /dev/null') - msignals.display(_("DynDNS cron installed."), 'success') + msignals.display(m18n.n('dyndns_cron_installed'), 'success') def dyndns_removecron(): @@ -163,6 +166,6 @@ def dyndns_removecron(): try: os.remove("/etc/cron.d/yunohost-dyndns") except: - raise MoulinetteError(167,_("DynDNS cron was not installed")) + raise MoulinetteError(errno.EIO, m18n.n('dyndns_cron_remove_failed')) - msignals.display(_("DynDNS cron removed."), 'success') + msignals.display(m18n.n('dyndns_cron_removed'), 'success') diff --git a/lib/yunohost/firewall.py b/lib/yunohost/firewall.py index 68089c94..b5c2a478 100644 --- a/lib/yunohost/firewall.py +++ b/lib/yunohost/firewall.py @@ -26,6 +26,7 @@ import os import sys import yaml +import errno try: import miniupnpc except ImportError: @@ -66,7 +67,7 @@ def firewall_allow(port=None, protocol='TCP', ipv6=False, no_upnp=False): if port not in firewall[ipv][protocol]: firewall[ipv][protocol].append(port) else: - msignals.display(_("Port already openned: %d" % port), 'warning') + msignals.display(m18n.n('port_already_opened') % port, 'warning') with open('/etc/yunohost/firewall.yml', 'w') as f: yaml.safe_dump(firewall, f, default_flow_style=False) @@ -102,7 +103,7 @@ def firewall_disallow(port=None, protocol='TCP', ipv6=False): if port in firewall[ipv][protocol]: firewall[ipv][protocol].remove(port) else: - msignals.display(_("Port already closed: %d" % port), 'warning') + msignals.display(m18n.n('port_already_closed') % port, 'warning') with open('/etc/yunohost/firewall.yml', 'w') as f: yaml.safe_dump(firewall, f, default_flow_style=False) @@ -140,7 +141,7 @@ def firewall_reload(): # IPv4 if os.system("iptables -P INPUT ACCEPT") != 0: - raise MoulinetteError(1, _("You cannot play with iptables here. You are either in a container or your kernel does not support it.")) + raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable')) if upnp: try: upnpc = miniupnpc.UPnP() @@ -154,9 +155,9 @@ def firewall_reload(): except: pass upnpc.addportmapping(port, protocol, upnpc.lanaddr, port, 'yunohost firewall : port %d' % port, '') else: - raise MoulinetteError(1, _("No uPnP device found")) + raise MoulinetteError(errno.ENXIO, m18n.n('upnp_dev_not_found')) except: - msignals.display(_("An error occured during uPnP port openning"), 'warning') + msignals.display(m18n.n('upnp_port_open_failed'), 'warning') os.system("iptables -F") os.system("iptables -X") @@ -196,7 +197,7 @@ def firewall_reload(): os.system("ip6tables -P INPUT DROP") os.system("service fail2ban restart") - msignals.display(_("Firewall successfully reloaded"), 'success') + msignals.display(m18n.n('firewall_reloaded'), 'success') return firewall_list() @@ -209,7 +210,6 @@ def firewall_upnp(action=None): action -- enable/disable """ - firewall = firewall_list(raw=True) if action: @@ -221,7 +221,7 @@ def firewall_upnp(action=None): with open('/etc/cron.d/yunohost-firewall', 'w+') as f: f.write('*/50 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin yunohost firewall reload >>/dev/null') - msignals.display(_("uPnP successfully enabled"), 'success') + msignals.display(m18n.n('upnp_enabled'), 'success') if action == 'disable': firewall['uPnP']['enabled'] = False @@ -242,7 +242,7 @@ def firewall_upnp(action=None): try: os.remove('/etc/cron.d/yunohost-firewall') except: pass - msignals.display(_("uPnP successfully disabled"), 'success') + msignals.display(m18n.n('upnp_disabled'), 'success') if action: os.system("cp /etc/yunohost/firewall.yml /etc/yunohost/firewall.yml.old") @@ -260,7 +260,7 @@ def firewall_stop(): """ if os.system("iptables -P INPUT ACCEPT") != 0: - raise MoulinetteError(1, _("You cannot play with iptables here. You are either in a container or your kernel does not support it.")) + raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable')) os.system("iptables -F") os.system("iptables -X") diff --git a/lib/yunohost/hook.py b/lib/yunohost/hook.py index b47dba0d..9c020bf9 100644 --- a/lib/yunohost/hook.py +++ b/lib/yunohost/hook.py @@ -27,6 +27,7 @@ import os import sys import re import json +import errno from moulinette.helpers import colorize from moulinette.core import MoulinetteError @@ -111,7 +112,7 @@ def hook_check(file): with open(file[:file.index('scripts/')] + 'manifest.json') as f: manifest = json.loads(str(f.read())) except: - raise MoulinetteError(22, _("Invalid app package")) + raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid')) action = file[file.index('scripts/') + 8:] if 'arguments' in manifest and action in manifest['arguments']: @@ -140,7 +141,9 @@ def hook_exec(file, args=None): for arg in required_args: if arg['name'] in args: if 'choices' in arg and args[arg['name']] not in arg['choices']: - raise MoulinetteError(22, _("Invalid choice") + ': ' + args[arg['name']]) + raise MoulinetteError(errno.EINVAL, + m18n.n('hook_choice_invalid') + % args[arg['name']]) arg_list.append(args[arg['name']]) else: if os.isatty(1) and 'ask' in arg: @@ -159,7 +162,9 @@ def hook_exec(file, args=None): elif 'default' in arg: arg_list.append(arg['default']) else: - raise MoulinetteError(22, _("Missing argument : %s") % arg['name']) + raise MoulinetteError(errno.EINVAL, + m18n.n('hook_argument_missing') + % arg['name']) file_path = "./" if "/" in file and file[0:2] != file_path: diff --git a/lib/yunohost/locales/en.json b/lib/yunohost/locales/en.json new file mode 100644 index 00000000..40b1f9a4 --- /dev/null +++ b/lib/yunohost/locales/en.json @@ -0,0 +1,127 @@ +{ + "yunohost" : "YunoHost", + + "upgrade_complete" : "Upgrade complete", + "installation_complete" : "Installation complete", + "installation_failed" : "Installation failed", + + "no_list_found" : "No list found", + "custom_list_name_required" : "You must provide a name for your custom list", + "list_retrieve_error" : "Unable to retrieve the remote list", + "list_feteched" : "List successfully fetched", + "list_unknown" : "Unknown list", + "list_removed" : "List successfully removed", + "app_unknown" : "Unknown app", + "app_no_upgrade" : "No app to upgrade", + "app_not_installed" : "%s is not installed", + "custom_app_url_required" : "You must provide an URL to upgrade your custom app %s", + "app_recent_version_required" : "%s requires a more recent version of the moulinette", + "app_upgraded" : "%s successfully upgraded", + "app_id_invalid" : "Invalid app id", + "app_already_installed" : "%s is already installed", + "app_removed" : "%s successfully removed", + "app_location_already_used" : "An app is already installed on this location", + "app_location_install_failed" : "Unable to install the app on this location", + "app_extraction_failed" : "Unable to extract installation files", + "app_install_files_invalid" : "Invalid installation files", + "app_manifest_invalid" : "Invalid app manifest", + "app_sources_fetch_failed" : "Unable to fetch sources files", + "ssowat_conf_updated" : "SSOwat persistent configuration successfully updated", + "ssowat_conf_generated" : "SSOwat configuration successfully generated", + "mysql_db_creation_failed" : "MySQL database creation failed", + "mysql_db_init_failed" : "MySQL database init failed", + "mysql_db_initialized" : "MySQL database successfully initialized", + "extracting" : "Extracting...", + "downloading" : "Downloading...", + "done" : "Done.", + + "domain_unknown" : "Unknown domain", + "domain_dyndns_invalid" : "Invalid domain to use with DynDNS", + "domain_dyndns_already_subscribed" : "You already have subscribed to a DynDNS domain", + "domain_dyndns_root_unknown" : "Unknown DynDNS root domain", + "domain_cert_gen_failed" : "Unable to generate certificate", + "domain_exists" : "Domain already exists", + "domain_zone_exists" : "Zone file already exists", + "domain_creation_failed" : "Unable to create domain", + "domain_created" : "Domain successfully created", + "domain_uninstall_app_first" : "One or more apps are installed on this domain. Please uninstall them before proceed to domain removal.", + "domain_deletion_failed" : "Unable to delete domain", + "domain_deleted" : "Domain successfully deleted", + + "dyndns_unavailable" : "Unavailable DynDNS subdomain", + "dyndns_registration_failed" : "Unable to register DynDNS domain: %s", + "dyndns_registered" : "DynDNS domain successfully registered", + "dyndns_ip_update_failed" : "Unable to update IP address on DynDNS", + "dyndns_ip_updated" : "IP address successfully updated on DynDNS", + "dyndns_cron_installed" : "DynDNS cron job successfully installed", + "dyndns_cron_remove_failed" : "Unable to remove DynDNS cron job", + "dyndns_cron_removed" : "DynDNS cron job successfully removed", + + "port_available" : "Port %d is available", + "port_unavailable" : "Port %d is not available", + "port_already_opened" : "Port %d is already opened", + "port_already_closed" : "Port %d is already closed", + "iptables_unavailable" : "You cannot play with iptables here. You are either in a container or your kernel does not support it.", + "upnp_dev_not_found" : "No uPnP device found", + "upnp_port_open_failed" : "Unable to open uPnP ports", + "upnp_enabled" : "uPnP successfully enabled", + "upnp_disabled" : "uPnP successfully disabled", + "firewall_reloaded" : "Firewall successfully reloaded", + + "hook_choice_invalid" : "Invalid choice '%s'", + "hook_argument_missing" : "Missing argument '%s'", + + "mountpoint_unknown" : "Unknown mountpoint", + "unit_unknown" : "Unknown unit '%s'", + "monitor_period_invalid" : "Invalid time period", + "monitor_stats_no_update" : "No monitoring statistics to update", + "monitor_stats_file_not_found" : "Statistics file not found", + "monitor_stats_period_unavailable" : "No available statistics for the period", + "monitor_enabled" : "Server monitoring successfully enabled", + "monitor_disabled" : "Server monitoring successfully disabled", + "monitor_not_enabled" : "Server monitoring is not enabled", + "monitor_glances_con_failed" : "Unable to connect to Glances server", + + "service_unknown" : "Unknown service '%s'", + "service_start_failed" : "Unable to start service '%s'", + "service_already_started" : "Service '%s' is already started", + "service_started" : "Service '%s' successfully started", + "service_stop_failed" : "Unable to stop service '%s'", + "service_already_stopped" : "Service '%s' is already stopped", + "service_stopped" : "Service '%s' successfully stopped", + "service_enable_failed" : "Unable to enable service '%s'", + "service_enabled" : "Service '%s' successfully enabled", + "service_disable_failed" : "Unable to disable service '%s'", + "service_disabled" : "Service '%s' successfully disabled", + "service_status_failed" : "Unable to determine status of service '%s'", + "service_no_log" : "No log to display for service '%s'", + "service_cmd_exec_failed" : "Unable to execute command '%s'", + + "ldap_initialized" : "LDAP successfully initialized", + "password_too_short" : "Password is too short", + "admin_password_change_failed" : "Unable to change password", + "admin_password_changed" : "Administration password successfully changed", + "maindomain_change_failed" : "Unable to change main domain", + "maindomain_changed" : "Main domain successfully changed", + "yunohost_installing" : "Installing YunoHost...", + "yunohost_already_installed" : "YunoHost is already installed", + "yunohost_ca_creation_failed" : "Unable to create certificate authority", + "yunohost_configured" : "YunoHost successfully configured", + "update_cache_failed" : "Unable to update APT cache", + "system_no_upgrade" : "There is no packages to upgrade", + "system_upgraded" : "System successfully upgraded", + + "field_invalid" : "Invalid field '%s'", + "mail_domain_unknown" : "Unknown mail address domain '%s'", + "mail_alias_remove_failed" : "Unable to remove mail alias '%s'", + "mail_forward_remove_failed" : "Unable to remove mail forward '%s'", + "user_unknown" : "Unknown user", + "user_creation_failed" : "Unable to create user", + "user_created" : "User successfully created", + "user_deletion_failed" : "Unable to delete user", + "user_deleted" : "User successfully deleted", + "user_update_failed" : "Unable to update user", + "user_updated" : "User successfully updated", + "user_info_failed" : "Unable to retrieve user information" +} + diff --git a/lib/yunohost/monitor.py b/lib/yunohost/monitor.py index d831befb..a2e7f8a1 100644 --- a/lib/yunohost/monitor.py +++ b/lib/yunohost/monitor.py @@ -31,6 +31,7 @@ import calendar import subprocess import xmlrpclib import os.path +import errno import cPickle as pickle from urllib import urlopen from datetime import datetime, timedelta @@ -74,7 +75,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False): result_dname = dn if len(devices) == 0: if mountpoint is not None: - raise MoulinetteError(1, _("Unknown mountpoint '%s'") % mountpoint) + raise MoulinetteError(errno.ENODEV, m18n.n('mountpoint_unknown')) return result # Retrieve monitoring for unit(s) @@ -131,7 +132,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False): for dname in devices_names: _set(dname, 'not-available') else: - raise MoulinetteError(1, _("Unknown unit '%s'") % u) + raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown') % u) if result_dname is not None: return result[result_dname] @@ -203,7 +204,7 @@ def monitor_network(units=None, human_readable=False): 'gateway': gateway } else: - raise MoulinetteError(1, _("Unknown unit '%s'") % u) + raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown') % u) if len(units) == 1: return result[units[0]] @@ -253,7 +254,7 @@ def monitor_system(units=None, human_readable=False): elif u == 'infos': result[u] = json.loads(glances.getSystem()) else: - raise MoulinetteError(1, _("Unknown unit '%s'") % u) + raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown') % u) if len(units) == 1 and type(result[units[0]]) is not str: return result[units[0]] @@ -269,7 +270,7 @@ def monitor_update_stats(period): """ if period not in ['day', 'week', 'month']: - raise MoulinetteError(22, _("Invalid period")) + raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid')) stats = _retrieve_stats(period) if not stats: @@ -287,7 +288,7 @@ def monitor_update_stats(period): else: monitor = _monitor_all(p, 0) if not monitor: - raise MoulinetteError(1, _("No monitoring statistics to update")) + raise MoulinetteError(errno.ENODATA, m18n.n('monitor_stats_no_update')) stats['timestamp'].append(time.time()) @@ -352,13 +353,15 @@ def monitor_show_stats(period, date=None): """ if period not in ['day', 'week', 'month']: - raise MoulinetteError(22, _("Invalid period")) + raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid')) result = _retrieve_stats(period, date) if result is False: - raise MoulinetteError(167, _("Stats file not found")) + raise MoulinetteError(errno.ENOENT, + m18n.n('monitor_stats_file_not_found')) elif result is None: - raise MoulinetteError(1, _("No available stats for the given period")) + raise MoulinetteError(errno.EINVAL, + m18n.n('monitor_stats_period_unavailable')) return result @@ -389,7 +392,7 @@ def monitor_enable(no_stats=False): os.system("touch %s" % crontab_path) os.system("echo '%s' >%s" % (rules, crontab_path)) - msignals.display(_("Server monitoring successfully enabled."), 'success') + msignals.display(m18n.n('monitor_enabled'), 'success') def monitor_disable(): @@ -407,7 +410,7 @@ def monitor_disable(): try: service_disable('glances') except MoulinetteError as e: - msignals.display('%s.' % e.strerror, 'warning') + msignals.display(e.strerror, 'warning') # Remove crontab try: @@ -415,7 +418,7 @@ def monitor_disable(): except: pass - msignals.display(_("Server monitoring successfully disabled."), 'success') + msignals.display(m18n.n('monitor_disabled'), 'success') def _get_glances_api(): @@ -434,8 +437,8 @@ def _get_glances_api(): from yunohost.service import service_status if service_status('glances')['status'] != 'running': - raise MoulinetteError(1, _("Monitoring is disabled")) - raise MoulinetteError(1, _("Connection to Glances server failed")) + raise MoulinetteError(errno.EPERM, m18n.n('monitor_not_enabled')) + raise MoulinetteError(errno.EIO, m18n.n('monitor_glances_con_failed')) def _extract_inet(string, skip_netmask=False, skip_loopback=True): diff --git a/lib/yunohost/service.py b/lib/yunohost/service.py index ff796bb6..c2582c4d 100644 --- a/lib/yunohost/service.py +++ b/lib/yunohost/service.py @@ -26,6 +26,7 @@ import yaml import glob import subprocess +import errno import os.path from moulinette.core import MoulinetteError @@ -43,11 +44,12 @@ def service_start(names): names = [names] for name in names: if _run_service_command('start', name): - msignals.display(_("Service '%s' successfully started.") % name, 'success') + msignals.display(m18n.n('service_started') % name, 'success') else: if service_status(name)['status'] != 'running': - raise MoulinetteError(1, _("Starting of service '%s' failed") % name) - msignals.display(_("Service '%s' already started.") % name) + raise MoulinetteError(errno.EPERM, + m18n.n('service_start_failed') % name) + msignals.display(m18n.n('service_already_started') % name) def service_stop(names): @@ -62,11 +64,12 @@ def service_stop(names): names = [names] for name in names: if _run_service_command('stop', name): - msignals.display(_("Service '%s' successfully stopped.") % name, 'success') + msignals.display(m18n.n('service_stopped') % name, 'success') else: if service_status(name)['status'] != 'inactive': - raise MoulinetteError(1, _("Stopping of service '%s' failed") % name) - msignals.display(_("Service '%s' already stopped.") % name) + raise MoulinetteError(errno.EPERM, + m18n.n('service_stop_failed') % name) + msignals.display(m18n.n('service_already_stopped') % name) def service_enable(names): @@ -81,9 +84,10 @@ def service_enable(names): names = [names] for name in names: if _run_service_command('enable', name): - msignals.display(_("Service '%s' successfully enabled.") % name, 'success') + msignals.display(m18n.n('service_enabled') % name, 'success') else: - raise MoulinetteError(1, _("Enabling of service '%s' failed") % name) + raise MoulinetteError(errno.EPERM, + m18n.n('service_enable_failed') % name) def service_disable(names): @@ -98,9 +102,10 @@ def service_disable(names): names = [names] for name in names: if _run_service_command('disable', name): - msignals.display(_("Service '%s' successfully disabled.") % name, 'success') + msignals.display(m18n.n('service_disabled') % name, 'success') else: - raise MoulinetteError(1, _("Disabling of service '%s' failed") % name) + raise MoulinetteError(errno.EPERM, + m18n.n('service_disable_failed') % name) def service_status(names=[]): @@ -123,7 +128,8 @@ def service_status(names=[]): for name in names: if check_names and name not in services.keys(): - raise MoulinetteError(1, _("Unknown service '%s'") % name) + raise MoulinetteError(errno.EINVAL, + m18n.n('service_unknown') % name) status = None if services[name]['status'] == 'service': @@ -145,8 +151,8 @@ def service_status(names=[]): result[name]['status'] = 'inactive' else: # TODO: Log output? - msignals.display(_("Could not determine status of service '%s'.") % \ - name, 'warning') + msignals.display(m18n.n('service_status_failed') % name, + 'warning') else: result[name]['status'] = 'running' @@ -169,14 +175,14 @@ def service_log(name, number=50): Log every log files of a service Keyword argument: - name -- Services name to log + name -- Service name to log number -- Number of lines to display """ services = _get_services() if name not in services.keys(): - raise MoulinetteError(1, _("Unknown service '%s'") % service) + raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown') % name) if 'log' in services[name]: log_list = services[name]['log'] @@ -191,7 +197,7 @@ def service_log(name, number=50): else: result[log_path] = _tail(log_path, int(number)) else: - raise MoulinetteError(1, _("Nothing to log for service '%s'") % name) + raise MoulinetteError(errno.EPERM, m18n.n('service_no_log') % name) return result @@ -206,7 +212,7 @@ def _run_service_command(action, service): """ if service not in _get_services().keys(): - raise MoulinetteError(1, _("Unknown service '%s'") % service) + raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown') % name) cmd = None if action in ['start', 'stop']: @@ -215,14 +221,14 @@ def _run_service_command(action, service): arg = 'defaults' if action == 'enable' else 'remove' cmd = 'update-rc.d %s %s' % (service, arg) else: - raise MoulinetteError(1, _("Unknown action '%s'") % action) + raise ValueError("Unknown action '%s'" % action) try: ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: # TODO: Log output? - msignals.display(_("Execution of command '%s' failed.") % \ - ' '.join(e.cmd), 'warning') + msignals.display(m18n.n('service_cmd_exec_failed') % ' '.join(e.cmd), + 'warning') return False return True diff --git a/lib/yunohost/tools.py b/lib/yunohost/tools.py index 617ea8a0..5c634b54 100644 --- a/lib/yunohost/tools.py +++ b/lib/yunohost/tools.py @@ -30,6 +30,7 @@ import re import getpass import requests import json +import errno import apt import apt.progress @@ -69,7 +70,7 @@ def tools_ldapinit(auth): auth.update('cn=admin', admin_dict) - msignals.display(_("LDAP has been successfully initialized"), 'success') + msignals.display(m18n.n('ldap_ initialized'), 'success') def tools_adminpw(old_password, new_password): @@ -83,7 +84,7 @@ def tools_adminpw(old_password, new_password): """ # Validate password length if len(new_password) < 4: - raise MoulinetteError(22, _("Password is too short")) + raise MoulinetteError(errno.EINVAL, m18n.n('password_too_short')) old_password.replace('"', '\\"') old_password.replace('&', '\\&') @@ -92,9 +93,10 @@ def tools_adminpw(old_password, new_password): result = os.system('ldappasswd -h localhost -D cn=admin,dc=yunohost,dc=org -w "%s" -a "%s" -s "%s"' % (old_password, old_password, new_password)) if result == 0: - msignals.display(_("Admin password has been changed"), 'success') + msignals.display(m18n.n('admin_password_changed'), 'success') else: - raise MoulinetteError(22, _("Invalid password")) + raise MoulinetteError(errno.EPERM, + m18n.n('admin_password_change_failed')) def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False): @@ -165,7 +167,8 @@ def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False): for command in command_list: if os.system(command) != 0: - raise MoulinetteError(17, _("There were a problem during domain changing")) + raise MoulinetteError(errno.EPERM, + m18n.n('maindomain_change_failed')) if dyndns: dyndns_subscribe(domain=new_domain) elif len(new_domain.split('.')) >= 3: @@ -175,7 +178,7 @@ def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False): if dyndomain in dyndomains: dyndns_subscribe(domain=new_domain) - msignals.display(_("Main domain has been successfully changed"), 'success') + msignals.display(m18n.n('maindomain_changed'), 'success') def tools_postinstall(domain, password, dyndns=False): @@ -194,9 +197,9 @@ def tools_postinstall(domain, password, dyndns=False): try: with open('/etc/yunohost/installed') as f: pass except IOError: - print('Installing YunoHost') + msignals.display(m18n.n('yunohost_installing')) else: - raise MoulinetteError(17, _("YunoHost is already installed")) + raise MoulinetteError(errno.EPERM, m18n.n('yunohost_already_installed')) if len(domain.split('.')) >= 3: r = requests.get('http://dyndns.yunohost.org/domains') @@ -206,7 +209,8 @@ def tools_postinstall(domain, password, dyndns=False): if requests.get('http://dyndns.yunohost.org/test/%s' % domain).status_code == 200: dyndns=True else: - raise MoulinetteError(17, _("Domain is already taken")) + raise MoulinetteError(errno.EEXIST, + m18n.n('dyndns_unavailable')) # Create required folders folders_to_create = [ @@ -257,7 +261,8 @@ def tools_postinstall(domain, password, dyndns=False): for command in command_list: if os.system(command) != 0: - raise MoulinetteError(17, _("There were a problem during CA creation")) + raise MoulinetteError(errno.EPERM, + m18n.n('yunohost_ca_creation_failed')) # Initialize YunoHost LDAP base tools_ldapinit(auth) @@ -277,7 +282,7 @@ def tools_postinstall(domain, password, dyndns=False): os.system('touch /etc/yunohost/installed') os.system('service yunohost-api restart &') - msignals.display(_("YunoHost has been successfully configured"), 'success') + msignals.display(m18n.n('yunohost_configured'), 'success') def tools_update(ignore_apps=False, ignore_packages=False): @@ -296,7 +301,7 @@ def tools_update(ignore_apps=False, ignore_packages=False): cache = apt.Cache() # Update APT cache if not cache.update(): - raise MoulinetteError(1, _("An error occured during APT cache update")) + raise MoulinetteError(errno.EPERM, m18n.n('update_cache_failed')) cache.open(None) cache.upgrade(True) @@ -338,12 +343,11 @@ def tools_update(ignore_apps=False, ignore_packages=False): }) if len(apps) == 0 and len(packages) == 0: - msignals.display(_("There is nothing to upgrade right now"), 'success') + msignals.display(m18n.n('system_no_upgrade'), 'success') return { 'packages': packages, 'apps': apps } - def tools_upgrade(ignore_apps=False, ignore_packages=False): """ Update apps & package cache, then display changelog @@ -380,7 +384,7 @@ def tools_upgrade(ignore_apps=False, ignore_packages=False): app_upgrade() except: pass - msignals.display(_("System successfully upgraded"), 'success') + msignals.display(m18n.n('system_upgraded'), 'success') # Return API logs if it is an API call if not os.isatty(1): diff --git a/lib/yunohost/user.py b/lib/yunohost/user.py index 87b1482d..71a76f9e 100644 --- a/lib/yunohost/user.py +++ b/lib/yunohost/user.py @@ -29,6 +29,7 @@ import crypt import random import string import json +import errno from moulinette.core import MoulinetteError @@ -64,7 +65,8 @@ def user_list(auth, fields=None, filter=None, limit=None, offset=None): if attr in keys: attrs.append(attr) else: - raise MoulinetteError(22, _("Invalid field '%s'") % attr) + raise MoulinetteError(errno.EINVAL, + m18n.n('field_invalid') % attr) else: attrs = [ 'uid', 'cn', 'mail' ] @@ -99,7 +101,7 @@ def user_create(auth, username, firstname, lastname, mail, password): # Validate password length if len(password) < 4: - raise MoulinetteError(22, _("Password is too short")) + raise MoulinetteError(errno.EINVAL, m18n.n('password_too_short')) auth.validate_uniqueness({ 'uid' : username, @@ -107,10 +109,11 @@ def user_create(auth, username, firstname, lastname, mail, password): }) if mail[mail.find('@')+1:] not in domain_list(auth)['domains']: - raise MoulinetteError(22, _("Unknown domain '%s'") % mail[mail.find('@')+1:]) + raise MoulinetteError(errno.EINVAL, + m18n.n('mail_domain_unknown') + % mail[mail.find('@')+1:]) # Get random UID/GID - uid_check = gid_check = 0 while uid_check == 0 and gid_check == 0: uid = str(random.randint(200, 99999)) @@ -174,12 +177,12 @@ def user_create(auth, username, firstname, lastname, mail, password): os.system("su - %s -c ''" % username) os.system('yunohost app ssowatconf > /dev/null 2>&1') #TODO: Send a welcome mail to user - msignals.display(_("User '%s' successfully created.") % username, 'success') + msignals.display(m18n.n('user_created'), 'success') hook_callback('post_user_create', [username, mail, password, firstname, lastname]) return { 'fullname' : fullname, 'username' : username, 'mail' : mail } - raise MoulinetteError(169, _("An error occurred during user creation")) + raise MoulinetteError(169, m18n.n('user_creation_failed')) def user_delete(auth, users, purge=False): @@ -207,10 +210,10 @@ def user_delete(auth, users, purge=False): deleted.append(user) continue else: - raise MoulinetteError(169, _("An error occurred during user deletion")) + raise MoulinetteError(169, m18n.n('user_deletion_failed')) os.system('yunohost app ssowatconf > /dev/null 2>&1') - msignals.display(_("User(s) successfully deleted."), 'success') + msignals.display(m18n.n('user_deleted'), 'success') return { 'users': deleted } @@ -239,7 +242,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, change # Populate user informations result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch) if not result: - raise MoulinetteError(167, _("Unknown username '%s'") % username) + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown')) user = result[0] # Get modifications from arguments @@ -263,7 +266,9 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, change if mail: auth.validate_uniqueness({ 'mail': mail }) if mail[mail.find('@')+1:] not in domains: - raise MoulinetteError(22, _("Unknown domain '%s'") % mail[mail.find('@')+1:]) + raise MoulinetteError(errno.EINVAL, + m18n.n('mail_domain_unknown') + % mail[mail.find('@')+1:]) del user['mail'][0] new_attr_dict['mail'] = [mail] + user['mail'] @@ -273,7 +278,9 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, change for mail in add_mailalias: auth.validate_uniqueness({ 'mail': mail }) if mail[mail.find('@')+1:] not in domains: - raise MoulinetteError(22, _("Unknown domain '%s'") % mail[mail.find('@')+1:]) + raise MoulinetteError(errno.EINVAL, + m18n.n('mail_domain_unknown') + % mail[mail.find('@')+1:]) user['mail'].append(mail) new_attr_dict['mail'] = user['mail'] @@ -284,7 +291,8 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, change if len(user['mail']) > 1 and mail in user['mail'][1:]: user['mail'].remove(mail) else: - raise MoulinetteError(22, _("Invalid mail alias '%s'") % mail) + raise MoulinetteError(errno.EINVAL, + m18n.n('mail_alias_remove_failed') % mail) new_attr_dict['mail'] = user['mail'] if add_mailforward: @@ -303,14 +311,15 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None, change if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]: user['maildrop'].remove(mail) else: - raise MoulinetteError(22, _("Invalid mail forward '%s'") % mail) + raise MoulinetteError(errno.EINVAL, + m18n.n('mail_forward_remove_failed') % mail) new_attr_dict['maildrop'] = user['maildrop'] if auth.update('uid=%s,ou=users' % username, new_attr_dict): - msignals.display(_("User '%s' successfully updated.") % username, 'success') + msignals.display(m18n.n('user_updated'), 'success') return user_info(auth, username) else: - raise MoulinetteError(169, _("An error occurred during user update")) + raise MoulinetteError(169, m18n.n('user_update_failed')) def user_info(auth, username): @@ -333,7 +342,7 @@ def user_info(auth, username): if result: user = result[0] else: - raise MoulinetteError(22, _("Unknown username/mail '%s'") % username) + raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown')) result_dict = { 'username': user['uid'][0], @@ -352,4 +361,4 @@ def user_info(auth, username): if result: return result_dict else: - raise MoulinetteError(167, _("No user found")) + raise MoulinetteError(167, m18n.n('user_info_failed')) diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 00000000..8a75fec5 --- /dev/null +++ b/locales/en.json @@ -0,0 +1,26 @@ +{ + "colon" : "%s: ", + + "success" : "Success!", + "warning" : "Warning:", + "error" : "Error:", + "permission_denied" : "Permission denied", + "root_required" : "You must be root to perform this action", + "instance_already_running" : "An instance is already running", + + "unable_authenticate" : "Unable to authenticate", + "unable_retrieve_session" : "Unable to retrieve the session", + "error_ldap_operation" : "An error occured during LDAP operation", + + "password" : "Password", + "invalid_password" : "Invalid password", + "confirm" : "Confirm", + "values_mismatch" : "Values don't match", + "authentication_required_long" : "Authentication is required to perform this action", + "authentication_required" : "Authentication required", + "authentication_profile_required" : "Authentication to profile '%s' required", + "operation_interrupted" : "Operation interrupted", + + "not_logged_in" : "You are not logged in", + "server_already_running" : "A server is already running on that port" +} diff --git a/locales/fr.json b/locales/fr.json new file mode 100644 index 00000000..90a321ae --- /dev/null +++ b/locales/fr.json @@ -0,0 +1,26 @@ +{ + "colon" : "%s : ", + + "success" : "Succès !", + "warning" : "Attention :", + "error" : "Erreur :", + "permission_denied" : "Permission refusée", + "root_required" : "Vous devez avoir les droits super-utilisateur pour exécuter cette action", + "instance_already_running" : "Une instance est déjà en cours d'exécution", + + "unable_authenticate" : "Impossible de vous authentifier", + "unable_retrieve_session" : "Impossible de récupérer la session", + "error_ldap_operation" : "Une erreur est survenue lors de l'opération LDAP", + + "password" : "Mot de passe", + "invalid_password" : "Mot de passe incorrect", + "confirm" : "Confirmez", + "values_mismatch" : "Les valeurs ne correspondent pas", + "authentication_required_long" : "L'authentification est requise pour exécuter cette action", + "authentication_required" : "Authentification requise", + "authentication_profile_required" : "Authentification au profile '%s' requise", + "operation_interrupted" : "Opération interrompue", + + "not_logged_in" : "Vous n'êtes pas connecté", + "server_already_running" : "Un server est déjà en cours d'exécution sur ce port" +} diff --git a/moulinette/__init__.py b/moulinette/__init__.py index 5c53cd6a..066a1f0b 100755 --- a/moulinette/__init__.py +++ b/moulinette/__init__.py @@ -50,13 +50,11 @@ def init(**kwargs): """ import sys import __builtin__ - from moulinette.core import Package, MoulinetteSignals, install_i18n + from moulinette.core import Package, Moulinette18n, MoulinetteSignals __builtin__.__dict__['pkg'] = Package(**kwargs) + __builtin__.__dict__['m18n'] = Moulinette18n(pkg) __builtin__.__dict__['msignals'] = MoulinetteSignals() - # Initialize internationalization - install_i18n() - # Add library directory to python path sys.path.insert(0, pkg.libdir) @@ -104,6 +102,6 @@ def cli(namespaces, args, use_cache=True): 'use_cache': use_cache}) moulinette.run(args) except MoulinetteError as e: - print(_('%s: %s' % (colorize(_('Error'), 'red'), e.strerror))) + print('%s %s' % (colorize(m18n.g('error'), 'red'), e.strerror)) return e.errno return 0 diff --git a/moulinette/actionsmap.py b/moulinette/actionsmap.py index 880ee420..5c35ceda 100644 --- a/moulinette/actionsmap.py +++ b/moulinette/actionsmap.py @@ -259,7 +259,7 @@ class ActionsMap(object): def __init__(self, parser, namespaces=[], use_cache=True): self.use_cache = use_cache if not issubclass(parser, BaseActionsMapParser): - raise MoulinetteError(errno.EINVAL, _("Invalid parser class '%s'" % parser.__name__)) + raise ValueError("Invalid parser class '%s'" % parser.__name__) self._parser_class = parser logging.debug("initializing ActionsMap for the interface '%s'" % parser.interface) @@ -311,7 +311,7 @@ class ActionsMap(object): try: auth = self.parser.get_global_conf('authenticator', profile)[1] except KeyError: - raise MoulinetteError(errno.EINVAL, _("Unknown authenticator profile '%s'") % profile) + raise ValueError("Unknown authenticator profile '%s'" % profile) else: return auth() @@ -343,9 +343,11 @@ class ActionsMap(object): fromlist=[func_name]) func = getattr(mod, func_name) except (AttributeError, ImportError): - raise MoulinetteError(errno.ENOSYS, _('Function is not defined')) + raise ImportError("Unable to load function %s.%s/%s" + % (namespace, category, func_name)) else: - # Process the action + # Load translation and process the action + m18n.load_namespace(namespace) return func(**arguments) @staticmethod diff --git a/moulinette/authenticators/__init__.py b/moulinette/authenticators/__init__.py index 601f7d06..b9c33134 100644 --- a/moulinette/authenticators/__init__.py +++ b/moulinette/authenticators/__init__.py @@ -93,7 +93,7 @@ class BaseAuthenticator(object): s_id, s_hash = token except TypeError: if not password: - raise MoulinetteError(errno.EINVAL, _("Invalid format for token")) + raise ValueError("Invalid token format") else: # TODO: Log error store_session = False @@ -108,9 +108,9 @@ class BaseAuthenticator(object): except MoulinetteError: raise except Exception as e: - logging.error("authentication (name: '%s', type: '%s') fails: %s" % \ - (self.name, self.vendor, e)) - raise MoulinetteError(errno.EACCES, _("Unable to authenticate")) + logging.error("authentication (name: '%s', type: '%s') fails: %s" \ + % (self.name, self.vendor, e)) + raise MoulinetteError(errno.EACCES, m18n.g('unable_authenticate')) # Store session if store_session: @@ -140,8 +140,8 @@ class BaseAuthenticator(object): with self._open_sessionfile(session_id, 'r') as f: enc_pwd = f.read() except IOError: - # TODO: Set proper error code - raise MoulinetteError(167, _("Unable to retrieve session")) + raise MoulinetteError(errno.ENOENT, + m18r.g('unable_retrieve_session')) else: gpg = gnupg.GPG() gpg.encoding = 'utf-8' diff --git a/moulinette/authenticators/ldap.py b/moulinette/authenticators/ldap.py index 9e81acf7..1855a79f 100644 --- a/moulinette/authenticators/ldap.py +++ b/moulinette/authenticators/ldap.py @@ -2,6 +2,7 @@ # TODO: Use Python3 to remove this fix! from __future__ import absolute_import +import errno import ldap import ldap.modlist as modlist @@ -64,7 +65,7 @@ class Authenticator(BaseAuthenticator): else: con.simple_bind_s() except ldap.INVALID_CREDENTIALS: - raise MoulinetteError(errno.EACCES, _("Invalid password")) + raise MoulinetteError(errno.EACCES, m18n.g('invalid_password')) else: self.con = con @@ -93,7 +94,7 @@ class Authenticator(BaseAuthenticator): try: result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs) except: - raise MoulinetteError(169, _('An error occured during LDAP search')) + raise MoulinetteError(169, m18n.g('error_ldap_operation')) result_list = [] if not attrs or 'dn' not in attrs: @@ -122,7 +123,7 @@ class Authenticator(BaseAuthenticator): try: self.con.add_s(dn, ldif) except: - raise MoulinetteError(169, _('An error occured during LDAP entry creation')) + raise MoulinetteError(169, m18n.g('error_ldap_operation')) else: return True @@ -141,7 +142,7 @@ class Authenticator(BaseAuthenticator): try: self.con.delete_s(dn) except: - raise MoulinetteError(169, _('An error occured during LDAP entry deletion')) + raise MoulinetteError(169, m18n.g('error_ldap_operation')) else: return True @@ -169,7 +170,7 @@ class Authenticator(BaseAuthenticator): self.con.modify_ext_s(dn, ldif) except: - raise MoulinetteError(169, _('An error occured during LDAP entry update')) + raise MoulinetteError(169, m18n.g('error_ldap_operation')) else: return True @@ -188,5 +189,5 @@ class Authenticator(BaseAuthenticator): if not self.search(filter=attr + '=' + value): continue else: - raise MoulinetteError(17, _('Attribute already exists') + ' "' + attr + '=' + value + '"') + raise MoulinetteError(17, 'Attribute already exists "%s=%s"' % (attr, value)) return True diff --git a/moulinette/core.py b/moulinette/core.py index 6ced832a..30de4efd 100644 --- a/moulinette/core.py +++ b/moulinette/core.py @@ -3,35 +3,14 @@ import os import sys import time +import json import errno -import gettext import logging from importlib import import_module # Package manipulation ------------------------------------------------- -def install_i18n(namespace=None): - """Install internationalization - - Install translation based on the package's default gettext domain or - on 'namespace' if provided. - - Keyword arguments: - - namespace -- The namespace to initialize i18n for - - """ - if namespace: - try: - t = gettext.translation(namespace, pkg.localedir) - except IOError: - # TODO: Log error - return - else: - t.install() - else: - gettext.install('moulinette', pkg.localedir) - class Package(object): """Package representation and easy access methods @@ -51,7 +30,7 @@ class Package(object): # Set local directories self._datadir = '%s/data' % basedir self._libdir = '%s/lib' % basedir - self._localedir = '%s/po' % basedir + self._localedir = '%s/locales' % basedir self._cachedir = '%s/cache' % basedir else: import package @@ -130,6 +109,189 @@ class Package(object): True if mode[0] == 'w' else False) return open('%s/%s' % (self.get_cachedir(**kwargs), filename), mode) + +# Internationalization ------------------------------------------------- + +class Translator(object): + """Internationalization class + + Provide an internationalization mechanism based on JSON files to + translate a key in the proper locale. + + Keyword arguments: + - locale_dir -- The directory where locale files are located + - default_locale -- The default locale to use + + """ + def __init__(self, locale_dir, default_locale='en'): + self.locale_dir = locale_dir + self.locale = default_locale + self._translations = {} + + # Attempt to load default translations + if not self._load_translations(default_locale): + raise ValueError("Unable to load locale '%s' from '%s'" + % (default_locale, locale_dir)) + self.default_locale = default_locale + + def get_locales(self): + """Return a list of the avalaible locales""" + locales = [] + + for f in os.listdir(self.locale_dir): + if f.endswith('.json'): + # TODO: Validate locale + locales.append(f[:-5]) + return locales + + def set_locale(self, locale): + """Set the locale to use + + Set the locale to use at first. If the locale is not available, + the default locale is used. + + Keyword arguments: + - locale -- The locale to use + + Returns: + True if the locale has been set, otherwise False + + """ + if locale not in self._translations: + if not self._load_translations(locale): + logging.info("unable to load locale '%s' from '%s'" + % (self.default_locale, self.locale_dir)) + + # Revert to default locale + self.locale = self.default_locale + return False + + # Set current locale + self.locale = locale + return True + + def translate(self, key): + """Retrieve proper translation for a key + + Attempt to retrieve translation for a key using the current locale + or the default locale if 'key' is not found. + + Keyword arguments: + - key -- The key to translate + + """ + try: + value = self._translations[self.locale][key] + except KeyError: + try: + value = self._translations[self.default_locale][key] + logging.info("untranslated key '%s' for locale '%s'" % + (key, self.locale)) + except KeyError: + logging.warning("unknown key '%s' for locale '%s'" % + (key, self.default_locale)) + return key + return value + + def _load_translations(self, locale, overwrite=False): + """Load translations for a locale + + Attempt to load translations for a given locale. If 'overwrite' is + True, translations will be loaded again. + + Keyword arguments: + - locale -- The locale to load + - overwrite -- True to overwrite existing translations + + Returns: + True if the translations have been loaded, otherwise False + + """ + if not overwrite and locale in self._translations: + return True + + try: + with open('%s/%s.json' % (self.locale_dir, locale), 'r') as f: + j = json.load(f) + except IOError: + return False + else: + self._translations[locale] = j + return True + + +class Moulinette18n(object): + """Internationalization service for the moulinette + + Manage internationalization and access to the proper keys translation + used in the moulinette and libraries. + + Keyword arguments: + - package -- The current Package instance + - default_locale -- The default locale to use + + """ + def __init__(self, package, default_locale='en'): + self.default_locale = default_locale + self.locale = default_locale + self.pkg = package + + # Init translators + self._global = Translator(self.pkg.localedir, default_locale) + self._namespace = None + + def load_namespace(self, namespace): + """Load the namespace to use + + Load and set translations of a given namespace. Those translations + are accessible with Moulinette18n.n(). + + Keyword arguments: + - namespace -- The namespace to load + + """ + if self._namespace and self._namespace[0] == namespace: + return + + self._namespace = (namespace, Translator('%s/%s/locales' + % (self.pkg.libdir, namespace), self.default_locale)) + self._namespace[1].set_locale(self.locale) + + def set_locale(self, locale): + """Set the locale to use""" + self.locale = locale + + self._global.set_locale(locale) + if self._namespace: + self._namespace[1].set_locale(locale) + + def g(self, key): + """Retrieve proper translation for a moulinette key + + Attempt to retrieve value for a key from moulinette translations + using the current locale or the default locale if 'key' is not found. + + Keyword arguments: + - key -- The key to translate + + """ + return self._global.translate(key) + + def n(self, key): + """Retrieve proper translation for a moulinette key + + Attempt to retrieve value for a key from loaded namespace translations + using the current locale or the default locale if 'key' is not found. + + Keyword arguments: + - key -- The key to translate + + """ + if not self._namespace: + raise RuntimeError("No namespace loaded for translation") + return self._namespace[1].translate(key) + + class MoulinetteSignals(object): """Signals connector for the moulinette @@ -256,16 +418,16 @@ def init_interface(name, kwargs={}, actionsmap={}): try: mod = import_module('moulinette.interfaces.%s' % name) - except ImportError: + except ImportError as e: # TODO: List available interfaces - raise MoulinetteError(errno.EINVAL, _("Unknown interface '%s'" % name)) + raise ImportError("Unable to load interface '%s': %s" % (name, str(e))) else: try: # Retrieve interface classes parser = mod.ActionsMapParser interface = mod.Interface except AttributeError as e: - raise MoulinetteError(errno.EFAULT, _("Invalid interface '%s': %s") % (name, e)) + raise ImportError("Invalid interface '%s': %s" % (name, e)) # Instantiate or retrieve ActionsMap if isinstance(actionsmap, dict): @@ -273,7 +435,7 @@ def init_interface(name, kwargs={}, actionsmap={}): elif isinstance(actionsmap, ActionsMap): amap = actionsmap else: - raise MoulinetteError(errno.EINVAL, _("Invalid actions map '%r'" % actionsmap)) + raise ValueError("Invalid actions map '%r'" % actionsmap) return interface(amap, **kwargs) @@ -293,7 +455,8 @@ def init_authenticator((vendor, name), kwargs={}): mod = import_module('moulinette.authenticators.%s' % vendor) except ImportError as e: # TODO: List available authenticators vendors - raise MoulinetteError(errno.EINVAL, _("Unable to load authenticator vendor '%s': %s") % (vendor, str(e))) + raise ImportError("Unable to load authenticator vendor '%s': %s" + % (vendor, str(e))) else: return mod.Authenticator(name, **kwargs) @@ -369,12 +532,13 @@ class MoulinetteLock(object): try: (open(self._lockfile, 'w')).close() except IOError: - raise MoulinetteError(errno.EPERM, _("Permission denied, did you forget 'sudo' ?")) + raise MoulinetteError(errno.EPERM, + '%s. %s.' % (m18n.g('permission_denied'), m18n.g('root_required'))) break if (time.time() - start_time) > self.timeout: - raise MoulinetteError(errno.EBUSY, _("An instance is already running for '%s'") \ - % self.namespace) + raise MoulinetteError(errno.EBUSY, + m18n.g('instance_already_running')) # Wait before checking again time.sleep(self.interval) self._locked = True diff --git a/moulinette/interfaces/api.py b/moulinette/interfaces/api.py index 22dbca93..be54ac3f 100644 --- a/moulinette/interfaces/api.py +++ b/moulinette/interfaces/api.py @@ -122,7 +122,7 @@ class _ActionsMapPlugin(object): try: kwargs['password'] = request.POST['password'] except KeyError: - raise HTTPBadRequestResponse(_("Missing password parameter")) + raise HTTPBadRequestResponse("Missing password parameter") try: kwargs['profile'] = request.POST['profile'] except KeyError: @@ -235,7 +235,7 @@ class _ActionsMapPlugin(object): try: del self.secrets[s_id] except KeyError: - raise HTTPUnauthorizedResponse(_("You are not logged in")) + raise HTTPUnauthorizedResponse(m18n.g('not_logged_in')) else: # TODO: Clean the session for profile only # Delete cookie and clean the session @@ -278,9 +278,9 @@ class _ActionsMapPlugin(object): secret=s_secret)[authenticator.name] except KeyError: if authenticator.name == 'default': - msg = _("Needing authentication") + msg = m18n.g('authentication_required') else: - msg = _("Needing authentication to profile '%s'") % authenticator.name + msg = m18n.g('authentication_profile_required') % authenticator.name raise HTTPUnauthorizedResponse(msg) else: return authenticator(token=(s_id, s_hash)) @@ -405,7 +405,7 @@ class ActionsMapParser(BaseActionsMapParser): auth = msignals.authenticate(klass(), **auth_conf) if not auth.is_authenticated: # TODO: Set proper error code - raise MoulinetteError(errno.EACCES, _("This action need authentication")) + raise MoulinetteError(errno.EACCES, m18n.g('authentication_required_long')) if self.get_conf(tid, 'argument_auth') and \ self.get_conf(tid, 'authenticate') == 'all': ret.auth = auth @@ -466,7 +466,8 @@ class Interface(BaseInterface): run(self._app, port=_port) except IOError as e: if e.args[0] == errno.EADDRINUSE: - raise MoulinetteError(errno.EADDRINUSE, _("A server is already running")) + raise MoulinetteError(errno.EADDRINUSE, + m18n.g('server_already_running')) raise diff --git a/moulinette/interfaces/cli.py b/moulinette/interfaces/cli.py index dc273357..c5b6b3c6 100644 --- a/moulinette/interfaces/cli.py +++ b/moulinette/interfaces/cli.py @@ -137,8 +137,8 @@ class ActionsMapParser(BaseActionsMapParser): # TODO: Catch errors auth = msignals.authenticate(klass(), **auth_conf) if not auth.is_authenticated: - # TODO: Set proper error code - raise MoulinetteError(errno.EACCES, _("This action need authentication")) + raise MoulinetteError(errno.EACCES, + m18n.g('authentication_required_long')) if self.get_conf(ret._tid, 'argument_auth') and \ self.get_conf(ret._tid, 'authenticate') == 'all': ret.auth = auth @@ -157,6 +157,12 @@ class Interface(BaseInterface): """ def __init__(self, actionsmap): + import locale + + # Set user locale + lang = locale.getdefaultlocale()[0] + m18n.set_locale(lang[:2]) + # Connect signals to handlers msignals.set_handler('authenticate', self._do_authenticate) msignals.set_handler('display', self._do_display) @@ -177,7 +183,7 @@ class Interface(BaseInterface): try: ret = self.actionsmap.process(args, timeout=5) except KeyboardInterrupt, EOFError: - raise MoulinetteError(errno.EINTR, _("Interrupted")) + raise MoulinetteError(errno.EINTR, m18n.g('operation_interrupted')) if isinstance(ret, dict): pretty_print_dict(ret) @@ -194,7 +200,7 @@ class Interface(BaseInterface): """ # TODO: Allow token authentication? - msg = help or _("Password") + msg = help or m18n.g('password') return authenticator(password=self._do_prompt(msg, True, False)) def _do_prompt(self, message, is_password, confirm): @@ -204,14 +210,15 @@ class Interface(BaseInterface): """ if is_password: - prompt = lambda m: getpass.getpass(colorize(_('%s: ') % m, 'blue')) + prompt = lambda m: getpass.getpass(colorize(m18n.g('colon') % m, + 'blue')) else: - prompt = lambda m: raw_input(colorize(_('%s: ') % m, 'blue')) + prompt = lambda m: raw_input(colorize(m18n.g('colon') % m, 'blue')) value = prompt(message) if confirm: - if prompt(_('Retype %s') % message) != value: - raise MoulinetteError(errno.EINVAL, _("Values don't match")) + if prompt('%s %s' % (m18n.g('confirm'), message)) != value: + raise MoulinetteError(errno.EINVAL, m18n.g('values_mismatch')) return value @@ -222,8 +229,8 @@ class Interface(BaseInterface): """ if style == 'success': - print('%s %s' % (colorize(_("Success!"), 'green'), message)) + print('%s %s' % (colorize(m18n.g('success'), 'green'), message)) elif style == 'warning': - print('%s %s' % (colorize(_("Warning!"), 'yellow'), message)) + print('%s %s' % (colorize(m18n.g('warning'), 'yellow'), message)) else: print(message)