Skip to content

Commit

Permalink
Merge pull request #52 from edx/andya/support-django-apps
Browse files Browse the repository at this point in the history
Support Django app directory structures
  • Loading branch information
andy-armstrong authored Jan 19, 2017
2 parents 01c5d6f + aaed1e4 commit 3906473
Show file tree
Hide file tree
Showing 58 changed files with 1,609 additions and 272 deletions.
9 changes: 6 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ running the following command inside the extracted directory.
Running
=======

Running commands from the edx-platform directory will default to loading the
configuration at ``./conf/locale/config.yaml``. You can specify a different
configuration file with the ``--config`` argument.
For Django projects, commands should be run from the root directory, and
the default configuration will be found at ``./conf/locale/config.yaml``.
For Django apps, commands should be run from the app's directory, and
the default configuration will be found at ``./locale/config.yaml``.

You can specify a different configuration file with the ``--config`` argument.


General Commands
Expand Down
9 changes: 4 additions & 5 deletions i18n/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import argparse
import sys

__version__ = '0.3.5'
__version__ = '0.3.6'


class Runner:
Expand All @@ -13,6 +13,7 @@ class Runner:
"""
def __init__(self):
self.args = sys.argv[1:]
self.configuration = None
self.parser = argparse.ArgumentParser()
self.parser.add_argument(
'--config',
Expand Down Expand Up @@ -45,8 +46,6 @@ def __call__(self, **kwargs):
args = self.parser.parse_known_args(self.args)[0]
for key, val in kwargs.items():
setattr(args, key, val)
if args.config:
config.CONFIGURATION = config.Configuration(args.config)
else:
config.CONFIGURATION = config.Configuration(config.LOCALE_DIR.joinpath('config.yaml').normpath())
root_dir = kwargs.get('root_dir')
self.configuration = config.Configuration(filename=args.config, root_dir=root_dir)
return self.run(args)
32 changes: 15 additions & 17 deletions i18n/branch_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,28 @@
"""
from __future__ import print_function

from i18n import config, Runner


def clean_conf_folder(locale):
"""Remove the configuration directory for `locale`"""
dirname = config.CONFIGURATION.get_messages_dir(locale)
dirname.removedirs_p()


def clean_configuration_directory():
"""
Remove the configuration directories for all locales
in CONFIGURATION.translated_locales
"""
for locale in config.CONFIGURATION.translated_locales:
clean_conf_folder(locale)
from i18n import Runner


class BranchCleanup(Runner):
"""
Class to clean up the branch
"""
def run(self, args):
clean_configuration_directory()
self.clean_configuration_directory()

def clean_configuration_directory(self):
"""
Remove the configuration directories for all locales
in CONFIGURATION.translated_locales
"""
for locale in self.configuration.translated_locales:
self.clean_conf_folder(locale)

def clean_conf_folder(self, locale):
"""Remove the configuration directory for `locale`"""
dirname = self.configuration.get_messages_dir(locale)
dirname.removedirs_p()

main = BranchCleanup() # pylint: disable=invalid-name

Expand Down
36 changes: 26 additions & 10 deletions i18n/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
# Typically this should be the 'edx-platform' directory.
BASE_DIR = Path('.').abspath() # pylint: disable=invalid-name

# LOCALE_DIR contains the locale files.
# Typically this should be 'edx-platform/conf/locale'
LOCALE_DIR = BASE_DIR.joinpath('conf', 'locale')
# The base filename for the configuration file.
BASE_CONFIG_FILENAME = 'config.yaml'


class Configuration(object):
Expand All @@ -30,16 +29,33 @@ class Configuration(object):
'TRANSIFEX_URL': 'https://www.transifex.com/open-edx/edx-platform/',
}

def __init__(self, filename=None):
self._filename = filename
self._config = self.read_config(filename)
def __init__(self, filename=None, root_dir=None):
self.root_dir = Path(root_dir) if root_dir else Path('.')
self._filename = (Path(filename) if filename else Configuration.default_config_filename(root_dir=root_dir))
self._config = self.read_config(self._filename)

@property
def locale_dir(self):
"""
Returns the locale directory for this configuration.
"""
return self._filename.parent

@staticmethod
def default_config_filename(root_dir=None):
"""
Returns the default name of the configuration file.
"""
root_dir = Path(root_dir) if root_dir else Path('.').abspath()
locale_dir = root_dir / 'locale'
if not os.path.exists(locale_dir):
locale_dir = root_dir / 'conf' / 'locale'
return locale_dir / BASE_CONFIG_FILENAME

def read_config(self, filename):
"""
Returns data found in config file (as dict), or raises exception if file not found
"""
if filename is None:
return {}
if not os.path.exists(filename):
raise Exception("Configuration file cannot be found: %s" % filename)
with open(filename) as stream:
Expand All @@ -55,7 +71,7 @@ def get_messages_dir(self, locale):
Returns the name of the directory holding the po files for locale.
Example: edx-platform/conf/locale/fr/LC_MESSAGES
"""
return LOCALE_DIR.joinpath(locale, 'LC_MESSAGES')
return self.locale_dir.joinpath(locale, 'LC_MESSAGES')

@property
def source_messages_dir(self):
Expand Down Expand Up @@ -104,4 +120,4 @@ def ltr_langs(self):
"""
return sorted(set(self.translated_locales) - set(self.rtl_langs))

CONFIGURATION = Configuration()
CONFIGURATION = None
10 changes: 5 additions & 5 deletions i18n/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import polib
from path import Path

from i18n import config, Runner
from i18n import Runner
from i18n.converter import Converter
from i18n.generate import clean_pofile

Expand Down Expand Up @@ -234,11 +234,11 @@ def run(self, args):
"""
Generate dummy strings for all source po files.
"""

source_messages_dir = config.CONFIGURATION.source_messages_dir
for locale, converter in zip(config.CONFIGURATION.dummy_locales, [Dummy(), Dummy2(), ArabicDummy()]):
configuration = self.configuration
source_messages_dir = configuration.source_messages_dir
for locale, converter in zip(configuration.dummy_locales, [Dummy(), Dummy2(), ArabicDummy()]):
print('Processing source language files into dummy strings, locale "{}"'.format(locale))
for source_file in config.CONFIGURATION.source_messages_dir.walkfiles('*.po'):
for source_file in configuration.source_messages_dir.walkfiles('*.po'):
if args.verbose:
print(' ', source_file.relpath())
make_dummy(source_messages_dir.joinpath(source_file), locale, converter)
Expand Down
42 changes: 22 additions & 20 deletions i18n/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from path import Path

from i18n import config, Runner
from i18n import Runner
from i18n.execute import execute
from i18n.segment import segment_pofiles

Expand All @@ -35,15 +35,16 @@
DEVNULL = open(os.devnull, 'wb')


def base(path1, *paths):
"""Return a relative path from config.BASE_DIR to path1 / paths[0] / ... """
return config.BASE_DIR.relpathto(path1.joinpath(*paths)) # pylint: disable=no-value-for-parameter


class Extract(Runner):
"""
Class used to extract source files
"""

def base(self, path1, *paths):
"""Return a relative path from config.BASE_DIR to path1 / paths[0] / ... """
root_dir = self.configuration.root_dir
return root_dir.relpathto(path1.joinpath(*paths)) # pylint: disable=no-value-for-parameter

def add_args(self):
"""
Adds arguments
Expand All @@ -62,9 +63,10 @@ def run(self, args):
Main entry point of script
"""
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
config.LOCALE_DIR.parent.makedirs_p()
configuration = self.configuration
configuration.locale_dir.parent.makedirs_p()
# pylint: disable=attribute-defined-outside-init
self.source_msgs_dir = config.CONFIGURATION.source_messages_dir
self.source_msgs_dir = configuration.source_messages_dir

# The extraction process clobbers django.po and djangojs.po.
# Save them so that it won't do that.
Expand Down Expand Up @@ -94,38 +96,38 @@ def run(self, args):
'. --output={output}'
)

babel_mako_cfg = base(config.LOCALE_DIR, 'babel_mako.cfg')
babel_mako_cfg = self.base(configuration.locale_dir, 'babel_mako.cfg')
if babel_mako_cfg.exists():
babel_mako_cmd = babel_cmd_template.format(
verbosity=babel_verbosity,
config=babel_mako_cfg,
output=base(config.CONFIGURATION.source_messages_dir, 'mako.po'),
output=self.base(configuration.CONFIGURATION.source_messages_dir, 'mako.po'),
)

execute(babel_mako_cmd, working_directory=config.BASE_DIR, stderr=stderr)
execute(babel_mako_cmd, working_directory=configuration.root_dir, stderr=stderr)

babel_underscore_cfg = base(config.LOCALE_DIR, 'babel_underscore.cfg')
babel_underscore_cfg = self.base(configuration.locale_dir, 'babel_underscore.cfg')
if babel_underscore_cfg.exists():
babel_underscore_cmd = babel_cmd_template.format(
verbosity=babel_verbosity,
config=babel_underscore_cfg,
output=base(config.CONFIGURATION.source_messages_dir, 'underscore.po'),
output=self.base(configuration.source_messages_dir, 'underscore.po'),
)

execute(babel_underscore_cmd, working_directory=config.BASE_DIR, stderr=stderr)
execute(babel_underscore_cmd, working_directory=configuration.root_dir, stderr=stderr)

makemessages = "django-admin.py makemessages -l en -v{}".format(args.verbose)
ignores = " ".join('--ignore="{}/*"'.format(d) for d in config.CONFIGURATION.ignore_dirs)
ignores = " ".join('--ignore="{}/*"'.format(d) for d in configuration.ignore_dirs)
if ignores:
makemessages += " " + ignores

# Extract strings from django source files (*.py, *.html, *.txt).
make_django_cmd = makemessages + ' -d django'
execute(make_django_cmd, working_directory=config.BASE_DIR, stderr=stderr)
execute(make_django_cmd, working_directory=configuration.root_dir, stderr=stderr)

# Extract strings from Javascript source files (*.js).
make_djangojs_cmd = makemessages + ' -d djangojs'
execute(make_djangojs_cmd, working_directory=config.BASE_DIR, stderr=stderr)
execute(make_djangojs_cmd, working_directory=configuration.root_dir, stderr=stderr)

# makemessages creates 'django.po'. This filename is hardcoded.
# Rename it to django-partial.po to enable merging into django.po later.
Expand All @@ -138,7 +140,7 @@ def run(self, args):
files_to_clean = set()

# Extract strings from third-party applications.
for app_name in config.CONFIGURATION.third_party:
for app_name in configuration.third_party:
# Import the app to find out where it is. Then use pybabel to extract
# from that directory.
app_module = importlib.import_module(app_name)
Expand All @@ -149,14 +151,14 @@ def run(self, args):
babel_cmd = 'pybabel {verbosity} extract -F {config} -c "Translators:" {app} -o {output}'
babel_cmd = babel_cmd.format(
verbosity=babel_verbosity,
config=config.LOCALE_DIR / 'babel_third_party.cfg',
config=configuration.locale_dir / 'babel_third_party.cfg',
app=app_name,
output=output_file,
)
execute(babel_cmd, working_directory=app_dir, stderr=stderr)

# Segment the generated files.
segmented_files = segment_pofiles("en")
segmented_files = segment_pofiles(configuration, "en")
files_to_clean.update(segmented_files)

# Finish each file.
Expand Down
32 changes: 17 additions & 15 deletions i18n/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@

from polib import pofile

from i18n import config, Runner
from i18n import Runner
from i18n.execute import execute

LOG = logging.getLogger(__name__)
DEVNULL = open(os.devnull, "wb")
DUPLICATE_ENTRY_PATTERN = re.compile('#-#-#-#-#.*#-#-#-#-#')


def merge(locale, target='django.po', sources=('django-partial.po',), fail_if_missing=True):
def merge(configuration, locale, target='django.po', sources=('django-partial.po',), fail_if_missing=True):
"""
For the given locale, merge the `sources` files to become the `target`
file. Note that the target file might also be one of the sources.
Expand All @@ -41,7 +41,7 @@ def merge(locale, target='django.po', sources=('django-partial.po',), fail_if_mi
"""
LOG.info('Merging %s locale %s', target, locale)
locale_directory = config.CONFIGURATION.get_messages_dir(locale)
locale_directory = configuration.get_messages_dir(locale)
try:
validate_files(locale_directory, sources)
except Exception: # pylint: disable=broad-except
Expand Down Expand Up @@ -71,12 +71,12 @@ def merge(locale, target='django.po', sources=('django-partial.po',), fail_if_mi
LOG.warning(" %s duplicates in %s, details in .dup file", len(duplicate_entries), target_filename)


def merge_files(locale, fail_if_missing=True):
def merge_files(configuration, locale, fail_if_missing=True):
"""
Merge all the files in `locale`, as specified in config.yaml.
"""
for target, sources in config.CONFIGURATION.generate_merge.items():
merge(locale, target, sources, fail_if_missing)
for target, sources in configuration.generate_merge.items():
merge(configuration, locale, target, sources, fail_if_missing)


def clean_pofile(path):
Expand Down Expand Up @@ -163,28 +163,30 @@ def run(self, args):
"""
logging.basicConfig(stream=sys.stdout, level=logging.INFO)

configuration = self.configuration
root_dir = args.root_dir
if args.ltr:
langs = config.CONFIGURATION.ltr_langs
langs = configuration.ltr_langs
elif args.rtl:
langs = config.CONFIGURATION.rtl_langs
langs = configuration.rtl_langs
else:
langs = config.CONFIGURATION.translated_locales
langs = configuration.translated_locales

for locale in langs:
merge_files(locale, fail_if_missing=args.strict)
merge_files(configuration, locale, fail_if_missing=args.strict)
# Dummy text is not required. Don't raise exception if files are missing.
for locale in config.CONFIGURATION.dummy_locales:
merge_files(locale, fail_if_missing=False)
for locale in configuration.dummy_locales:
merge_files(configuration, locale, fail_if_missing=False)
# Merge the source locale, so we have the canonical .po files.
if config.CONFIGURATION.source_locale not in langs:
merge_files(config.CONFIGURATION.source_locale, fail_if_missing=args.strict)
if configuration.source_locale not in langs:
merge_files(configuration, configuration.source_locale, fail_if_missing=args.strict)

compile_cmd = 'django-admin.py compilemessages -v{}'.format(args.verbose)
if args.verbose:
stderr = None
else:
stderr = DEVNULL
execute(compile_cmd, working_directory=config.BASE_DIR, stderr=stderr)
execute(compile_cmd, working_directory=root_dir, stderr=stderr)

main = Generate() # pylint: disable=invalid-name

Expand Down
Loading

0 comments on commit 3906473

Please sign in to comment.