Skip to content

Commit

Permalink
Replaces gettext loc with custom
Browse files Browse the repository at this point in the history
Adds tool to extract strings
Allows config file to specify locale
  • Loading branch information
zooba committed Feb 16, 2016
1 parent 1146a74 commit d014e9b
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 88 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ env/
.ptvs/

# Build results
bin/
obj/
dist/
MANIFEST
Expand Down
62 changes: 0 additions & 62 deletions .hgignore

This file was deleted.

30 changes: 30 additions & 0 deletions bin/extract-loc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#! /usr/bin/env python3

import os
import re
import subprocess
import sys

from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent / "src" / "azure" / "cli"
OUTPUT = ROOT / "locale" / "en-US" / "messages.txt"

print('Extracting from:', ROOT)

if not ROOT.is_dir():
print("Failed to locate 'azure/cli'")
sys.exit(1)

if not OUTPUT.parent.is_dir():
os.makedirs(str(OUTPUT.parent))

with open(str(OUTPUT), 'w', encoding='utf-8-sig') as f_out:
for path in ROOT.rglob('*.py'):
with open(str(path), 'r', encoding='utf-8') as f:
content = f.read()
for m in re.finditer('[^\w_]_\(("(.+)"|\'(.+)\')\)', content):
print('# From', path, ':', m.span()[0], file=f_out)
print('KEY:', m.group(2) or m.group(3), file=f_out)
print(m.group(2) or m.group(3), file=f_out)
print(file=f_out)
10 changes: 4 additions & 6 deletions src/azure/cli/_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import sys

from ._locale import get_file as locale_get_file
from ._logging import logging

# Named arguments are prefixed with one of these strings
Expand Down Expand Up @@ -63,15 +64,12 @@ class ArgumentParser(object):
def __init__(self, prog):
self.prog = prog
self.noun_map = {
'$doc': 'azure-cli',
'$doc': 'azure-cli.txt',
}
self.help_args = { '--help', '-h' }
self.complete_args = { '--complete' }
self.global_args = { '--verbose', '--debug' }

self.doc_source = './'
self.doc_suffix = '.txt'

def add_command(self, handler, name=None, description=None, args=None):
'''Registers a command that may be parsed by this parser.
Expand All @@ -98,7 +96,7 @@ def add_command(self, handler, name=None, description=None, args=None):
for n in nouns:
full_name += n
m = m.setdefault(n.lower(), {
'$doc': full_name
'$doc': full_name + ".txt"
})
full_name += '.'
m['$description'] = description or handler.__doc__
Expand Down Expand Up @@ -234,7 +232,7 @@ def _display_usage(self, nouns, noun_map, arguments, out=sys.stdout):
print(' {0:<{1}} - {2}'.format(a, maxlen, d), file=out)
print(file=out, flush=True)

doc_file = os.path.join(self.doc_source, noun_map['$doc'] + self.doc_suffix)
doc_file = locale_get_file(noun_map['$doc'])
try:
with open(doc_file, 'r') as f:
print(f.read(), file=out, flush=True)
Expand Down
30 changes: 30 additions & 0 deletions src/azure/cli/_locale.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os.path

from codecs import open

def install(locale_dir):
mapping = []

with open(os.path.join(locale_dir, "messages.txt"), 'r', encoding='utf-8-sig') as f:
for i in f:
if not i or i.startswith('#') or not i.strip():
continue
if i.startswith('KEY: '):
mapping.append((i[5:].strip(), None))
else:
mapping[-1] = (mapping[-1][0], i.strip())

translations = dict(mapping)
def _(key):
return translations.get(key) or '<NO_TRANSLATION:{}>'.format(key)
_.locale_dir = locale_dir

__builtins__['_'] = _

def get_file(name):
try:
src = _.locale_dir
except (NameError, AttributeError):
raise RuntimeError("localizations not installed")

return os.path.join(src, name)
28 changes: 15 additions & 13 deletions src/azure/cli/_logging.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging as _logging
import sys

__all__ = ['logging', 'configure_logging']

_CODE_LEVEL = _logging.INFO + 1

class Logger(_logging.Logger):
Expand Down Expand Up @@ -30,28 +32,26 @@ def _arg_name(arg):
return a.lower()

def configure_logging(argv, config):
# TODO: Configure logging handler to log messages to .py file
# Thinking here:
# INFO messages as Python code
# DEBUG messages (if -v) as comments
# WARNING/ERROR messages as clearly marked comments

# Currently we just use the default format
level = _logging.WARNING

# Load logging info from config
if config.get('verbose'):
level = _logging.INFO
if config.get('debug'):
level = _logging.DEBUG
logfile = config.get('log')

logfile = None
# Load logging info from arguments
# Also remove any arguments consumed so that the parser does not
# have to explicitly ignore them.
i = 0
while i < len(argv):
arg = _arg_name(argv[i])
if arg in ('v', 'verbose'):
level = _logging.INFO
level = min(_logging.INFO, level)
argv.pop(i)
elif arg in ('debug',):
level = _logging.DEBUG
level = min(_logging.DEBUG, level)
argv.pop(i)
elif arg in ('log',):
argv.pop(i)
Expand All @@ -61,20 +61,22 @@ def configure_logging(argv, config):
pass
else:
i += 1
logging.setLevel(_logging.INFO)

# Configure the console output handler
stderr_handler = _logging.StreamHandler(sys.stderr)
stderr_handler.formatter = _logging.Formatter('%(levelname)s: %(message)s')
stderr_handler.level = level
logging.level = stderr_handler.level = level
logging.handlers.append(stderr_handler)

if logfile and logfile.lower().endswith('.py'):
# Configure a handler that logs code to a Python script
py_handler = _logging.StreamHandler(open(logfile, 'w', encoding='utf-8'))
py_handler.formatter = PyFileFormatter()
py_handler.level = level if level == _logging.DEBUG else _logging.INFO
logging.handlers.append(py_handler)
elif logfile:
# Configure the handler that logs code to a text file
log_handler = _logging.StreamHandler(open(logfile, 'w', encoding='utf-8'))
log_handler.formatter = _logging.Formatter('[%(levelname)s:%(asctime)s] %(message)s')
log_handler.level = level if level == _logging.DEBUG else _logging.INFO
logging.handlers.append(log_handler)
logging.handlers.append(log_handler)
60 changes: 60 additions & 0 deletions src/azure/cli/locale/en-US/messages.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# From D:\Repos\azure-cli\src\azure\cli\_argparse.py : 1208
KEY: Argument {0} is required
Argument {0} is required

# From D:\Repos\azure-cli\src\azure\cli\_argparse.py : 6848
KEY: argument '{0}' does not take a value
argument '{0}' does not take a value

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1147
KEY: user password or service principal secret, will prompt if not given.
user password or service principal secret, will prompt if not given.

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1253
KEY: If given, log in as a service principal rather than a user.
If given, log in as a service principal rather than a user.

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1367
KEY: A PEM encoded certificate private key file.
A PEM encoded certificate private key file.

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1454
KEY: A hex encoded thumbprint of the certificate.
A hex encoded thumbprint of the certificate.

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1535
KEY: Tenant domain or ID to log into.
Tenant domain or ID to log into.

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1597
KEY: do not prompt for confirmation of PII storage.
do not prompt for confirmation of PII storage.

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1927
KEY: Unknown environment {0}
Unknown environment {0}

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2091
KEY: Tenant:
Tenant:

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2381
KEY: Password:
Password:

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2883
KEY: No subscriptions found for this account
No subscriptions found for this account

# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2991
KEY: Setting subscription %s as default
Setting subscription %s as default

# From D:\Repos\azure-cli\src\azure\cli\commands\storage.py : 268
KEY: the resource group name
the resource group name

# From D:\Repos\azure-cli\src\azure\cli\commands\storage.py : 332
KEY: the subscription id
the subscription id

12 changes: 6 additions & 6 deletions src/azure/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import gettext
import os

gettext.install("az", os.path.join(os.path.abspath(__file__), '..', 'locale'))

from ._argparse import ArgumentParser
from ._locale import install as locale_install
from ._logging import configure_logging, logging
from ._session import Session

Expand All @@ -13,6 +11,11 @@
# SESSION provides read-write session variables
SESSION = Session()

# Load the user's preferred locale from their configuration
LOCALE = CONFIG.get('locale', 'en-US')
locale_install(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'locale', LOCALE))


def main(args):
CONFIG.load(os.path.expanduser('~/az.json'))
SESSION.load(os.path.expanduser('~/az.sess'), max_age=3600)
Expand All @@ -22,9 +25,6 @@ def main(args):
parser = ArgumentParser("az")

import azure.cli.commands as commands
parser.doc_source = os.path.dirname(commands.__file__)
# TODO: detect language
parser.doc_suffix = '.en_US.txt'

# Find the first noun on the command line and only load commands from that
# module to improve startup time.
Expand Down

0 comments on commit d014e9b

Please sign in to comment.