Loading django/core/management/commands/makemessages.py +48 −91 Original line number Diff line number Diff line Loading @@ -19,28 +19,25 @@ STATUS_OK = 0 @total_ordering class TranslatableFile(object): def __init__(self, dirpath, file_name, locale_dir): def __init__(self, dirpath, file_name): self.file = file_name self.dirpath = dirpath self.locale_dir = locale_dir def __repr__(self): return "<TranslatableFile: %s>" % os.sep.join([self.dirpath, self.file]) def __eq__(self, other): return self.path == other.path return self.dirpath == other.dirpath and self.file == other.file def __lt__(self, other): return self.path < other.path if self.dirpath == other.dirpath: return self.file < other.file return self.dirpath < other.dirpath @property def path(self): return os.path.join(self.dirpath, self.file) def process(self, command, domain): def process(self, command, potfile, domain, keep_pot=False): """ Extract translatable literals from self.file for :param domain:, creating or updating the POT file. Extract translatable literals from self.file for :param domain: creating or updating the :param potfile: POT file. Uses the xgettext GNU gettext utility. """ Loading Loading @@ -94,6 +91,8 @@ class TranslatableFile(object): if status != STATUS_OK: if is_templatized: os.unlink(work_file) if not keep_pot and os.path.exists(potfile): os.unlink(potfile) raise CommandError( "errors happened while running xgettext on %s\n%s" % (self.file, errors)) Loading @@ -101,14 +100,11 @@ class TranslatableFile(object): # Print warnings command.stdout.write(errors) if msgs: # Write/append messages to pot file potfile = os.path.join(self.locale_dir, '%s.pot' % str(domain)) if is_templatized: old = '#: ' + work_file[2:] new = '#: ' + orig_file[2:] msgs = msgs.replace(old, new) write_pot_file(potfile, msgs) if is_templatized: os.unlink(work_file) Loading Loading @@ -236,21 +232,21 @@ class Command(NoArgsCommand): settings.configure(USE_I18N = True) self.invoked_for_django = False self.locale_paths = [] self.default_locale_path = None if os.path.isdir(os.path.join('conf', 'locale')): self.locale_paths = [os.path.abspath(os.path.join('conf', 'locale'))] self.default_locale_path = self.locale_paths[0] localedir = os.path.abspath(os.path.join('conf', 'locale')) self.invoked_for_django = True # Ignoring all contrib apps self.ignore_patterns += ['contrib/*'] elif os.path.isdir('locale'): localedir = os.path.abspath('locale') else: self.locale_paths.extend(list(settings.LOCALE_PATHS)) # Allow to run makemessages inside an app dir if os.path.isdir('locale'): self.locale_paths.append(os.path.abspath('locale')) if self.locale_paths: self.default_locale_path = self.locale_paths[0] raise CommandError("This script should be run from the Django Git " "tree or your project or app tree. If you did indeed run it " "from the Git checkout or your project or application, " "maybe you are just missing the conf/locale (in the django " "tree) or locale (for project and application) directory? It " "is not created automatically, you have to create it by hand " "if you want to enable i18n for your project or application.") # We require gettext version 0.15 or newer. output, errors, status = _popen('xgettext --version') Loading @@ -265,25 +261,24 @@ class Command(NoArgsCommand): "gettext 0.15 or newer. You are using version %s, please " "upgrade your gettext toolset." % match.group()) try: potfiles = self.build_potfiles() potfile = self.build_pot_file(localedir) # Build po files for each selected locale locales = [] if locale is not None: locales = locale.split(',') if not isinstance(locale, list) else locale locales += locale.split(',') if not isinstance(locale, list) else locale elif process_all: locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path)) locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir)) locales = [os.path.basename(l) for l in locale_dirs] try: for locale in locales: if self.verbosity > 0: self.stdout.write("processing locale %s\n" % locale) for potfile in potfiles: self.write_po_file(potfile, locale) finally: if not self.keep_pot: self.remove_potfiles() if not self.keep_pot and os.path.exists(potfile): os.unlink(potfile) def build_pot_file(self, localedir): file_list = self.find_files(".") Loading @@ -297,41 +292,9 @@ class Command(NoArgsCommand): f.process(self, potfile, self.domain, self.keep_pot) return potfile def build_potfiles(self): """Build pot files and apply msguniq to them""" file_list = self.find_files(".") self.remove_potfiles() for f in file_list: f.process(self, self.domain) potfiles = [] for path in self.locale_paths: potfile = os.path.join(path, '%s.pot' % str(self.domain)) if not os.path.exists(potfile): continue msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' % (self.wrap, self.location, potfile)) if errors: if status != STATUS_OK: raise CommandError( "errors happened while running msguniq\n%s" % errors) elif self.verbosity > 0: self.stdout.write(errors) with open(potfile, 'w') as fp: fp.write(msgs) potfiles.append(potfile) return potfiles def remove_potfiles(self): for path in self.locale_paths: pot_path = os.path.join(path, '%s.pot' % str(self.domain)) if os.path.exists(pot_path): os.unlink(pot_path) def find_files(self, root): """ Helper function to get all files in the given root. Also check that there is a matching locale dir for each file. Helper method to get all files in the given root. """ def is_ignored(path, ignore_patterns): Loading @@ -352,26 +315,12 @@ class Command(NoArgsCommand): dirnames.remove(dirname) if self.verbosity > 1: self.stdout.write('ignoring directory %s\n' % dirname) elif dirname == 'locale': dirnames.remove(dirname) self.locale_paths.insert(0, os.path.join(os.path.abspath(dirpath), dirname)) for filename in filenames: file_path = os.path.normpath(os.path.join(dirpath, filename)) if is_ignored(file_path, self.ignore_patterns): if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), self.ignore_patterns): if self.verbosity > 1: self.stdout.write('ignoring file %s in %s\n' % (filename, dirpath)) else: locale_dir = None for path in self.locale_paths: if os.path.abspath(dirpath).startswith(os.path.dirname(path)): locale_dir = path break if not locale_dir: locale_dir = self.default_locale_path if not locale_dir: raise CommandError( "Unable to find a locale path to store translations for file %s" % file_path) all_files.append(TranslatableFile(dirpath, filename, locale_dir)) all_files.append(TranslatableFile(dirpath, filename)) return sorted(all_files) def write_po_file(self, potfile, locale): Loading @@ -379,8 +328,16 @@ class Command(NoArgsCommand): Creates or updates the PO file for self.domain and :param locale:. Uses contents of the existing :param potfile:. Uses msgmerge, and msgattrib GNU gettext utilities. Uses mguniq, msgmerge, and msgattrib GNU gettext utilities. """ msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' % (self.wrap, self.location, potfile)) if errors: if status != STATUS_OK: raise CommandError( "errors happened while running msguniq\n%s" % errors) elif self.verbosity > 0: self.stdout.write(errors) basedir = os.path.join(os.path.dirname(potfile), locale, 'LC_MESSAGES') if not os.path.isdir(basedir): Loading @@ -388,6 +345,8 @@ class Command(NoArgsCommand): pofile = os.path.join(basedir, '%s.po' % str(self.domain)) if os.path.exists(pofile): with open(potfile, 'w') as fp: fp.write(msgs) msgs, errors, status = _popen('msgmerge %s %s -q "%s" "%s"' % (self.wrap, self.location, pofile, potfile)) if errors: Loading @@ -396,9 +355,7 @@ class Command(NoArgsCommand): "errors happened while running msgmerge\n%s" % errors) elif self.verbosity > 0: self.stdout.write(errors) else: msgs = open(potfile, 'r').read() if not self.invoked_for_django: elif not self.invoked_for_django: msgs = self.copy_plural_forms(msgs, locale) msgs = msgs.replace( "#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % self.domain, "") Loading docs/man/django-admin.1 +1 −2 Original line number Diff line number Diff line Loading @@ -193,8 +193,7 @@ Ignore files or directories matching this glob-style pattern. Use multiple times to ignore more (makemessages command). .TP .I \-\-no\-default\-ignore Don't ignore the common private glob-style patterns 'CVS', '.*', '*~' and '*.pyc' (makemessages command). Don't ignore the common private glob-style patterns 'CVS', '.*' and '*~' (makemessages command). .TP .I \-\-no\-wrap Don't break long message lines into several lines (makemessages command). Loading docs/ref/django-admin.txt +2 −2 Original line number Diff line number Diff line Loading @@ -472,7 +472,7 @@ Example usage:: Use the ``--ignore`` or ``-i`` option to ignore files or directories matching the given :mod:`glob`-style pattern. Use multiple times to ignore more. These patterns are used by default: ``'CVS'``, ``'.*'``, ``'*~'``, ``'*.pyc'`` These patterns are used by default: ``'CVS'``, ``'.*'``, ``'*~'`` Example usage:: Loading @@ -499,7 +499,7 @@ for technically skilled translators to understand each message's context. .. versionadded:: 1.6 Use the ``--keep-pot`` option to prevent django from deleting the temporary .pot files it generates before creating the .po file. This is useful for .pot file it generates before creating the .po file. This is useful for debugging errors which may prevent the final language files from being created. runfcgi [options] Loading docs/topics/i18n/translation.txt +16 −1 Original line number Diff line number Diff line Loading @@ -1543,9 +1543,24 @@ All message file repositories are structured the same way. They are: * ``$PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)`` To create message files, you use the :djadmin:`django-admin.py makemessages <makemessages>` tool. And you use :djadmin:`django-admin.py compilemessages <compilemessages>` tool. You only need to be in the same directory where the ``locale/`` directory is located. And you use :djadmin:`django-admin.py compilemessages <compilemessages>` to produce the binary ``.mo`` files that are used by ``gettext``. You can also run :djadmin:`django-admin.py compilemessages --settings=path.to.settings <compilemessages>` to make the compiler process all the directories in your :setting:`LOCALE_PATHS` setting. Finally, you should give some thought to the structure of your translation files. If your applications need to be delivered to other users and will be used in other projects, you might want to use app-specific translations. But using app-specific translations and project-specific translations could produce weird problems with :djadmin:`makemessages`: it will traverse all directories below the current path and so might put message IDs into a unified, common message file for the current project that are already in application message files. The easiest way out is to store applications that are not part of the project (and so carry their own translations) outside the project tree. That way, :djadmin:`django-admin.py makemessages <makemessages>`, when ran on a project level will only extract strings that are connected to your explicit project and not strings that are distributed independently. tests/regressiontests/i18n/commands/extraction.py +0 −44 Original line number Diff line number Diff line Loading @@ -5,13 +5,10 @@ import os import re import shutil from django.conf import settings from django.core import management from django.test import SimpleTestCase from django.test.utils import override_settings from django.utils.encoding import force_text from django.utils._os import upath from django.utils import six from django.utils.six import StringIO Loading Loading @@ -355,44 +352,3 @@ class MultipleLocaleExtractionTests(ExtractorTests): management.call_command('makemessages', locale='pt,de,ch', verbosity=0) self.assertTrue(os.path.exists(self.PO_FILE_PT)) self.assertTrue(os.path.exists(self.PO_FILE_DE)) class CustomLayoutExtractionTests(ExtractorTests): def setUp(self): self._cwd = os.getcwd() self.test_dir = os.path.join(os.path.dirname(upath(__file__)), 'project_dir') def test_no_locale_raises(self): os.chdir(self.test_dir) with six.assertRaisesRegex(self, management.CommandError, "Unable to find a locale path to store translations for file"): management.call_command('makemessages', locale=LOCALE, verbosity=0) @override_settings( LOCALE_PATHS=(os.path.join(os.path.dirname(upath(__file__)), 'project_dir/project_locale'),) ) def test_project_locale_paths(self): """ Test that: * translations for app containing locale folder are stored in that folder * translations outside of that app are in LOCALE_PATHS[0] """ os.chdir(self.test_dir) self.addCleanup(shutil.rmtree, os.path.join(settings.LOCALE_PATHS[0], LOCALE)) self.addCleanup(shutil.rmtree, os.path.join(self.test_dir, 'app_with_locale/locale', LOCALE)) management.call_command('makemessages', locale=LOCALE, verbosity=0) project_de_locale = os.path.join( self.test_dir, 'project_locale/de/LC_MESSAGES/django.po',) app_de_locale = os.path.join( self.test_dir, 'app_with_locale/locale/de/LC_MESSAGES/django.po',) self.assertTrue(os.path.exists(project_de_locale)) self.assertTrue(os.path.exists(app_de_locale)) with open(project_de_locale, 'r') as fp: po_contents = force_text(fp.read()) self.assertMsgId('This app has no locale directory', po_contents) self.assertMsgId('This is a project-level string', po_contents) with open(app_de_locale, 'r') as fp: po_contents = force_text(fp.read()) self.assertMsgId('This app has a locale directory', po_contents) Loading
django/core/management/commands/makemessages.py +48 −91 Original line number Diff line number Diff line Loading @@ -19,28 +19,25 @@ STATUS_OK = 0 @total_ordering class TranslatableFile(object): def __init__(self, dirpath, file_name, locale_dir): def __init__(self, dirpath, file_name): self.file = file_name self.dirpath = dirpath self.locale_dir = locale_dir def __repr__(self): return "<TranslatableFile: %s>" % os.sep.join([self.dirpath, self.file]) def __eq__(self, other): return self.path == other.path return self.dirpath == other.dirpath and self.file == other.file def __lt__(self, other): return self.path < other.path if self.dirpath == other.dirpath: return self.file < other.file return self.dirpath < other.dirpath @property def path(self): return os.path.join(self.dirpath, self.file) def process(self, command, domain): def process(self, command, potfile, domain, keep_pot=False): """ Extract translatable literals from self.file for :param domain:, creating or updating the POT file. Extract translatable literals from self.file for :param domain: creating or updating the :param potfile: POT file. Uses the xgettext GNU gettext utility. """ Loading Loading @@ -94,6 +91,8 @@ class TranslatableFile(object): if status != STATUS_OK: if is_templatized: os.unlink(work_file) if not keep_pot and os.path.exists(potfile): os.unlink(potfile) raise CommandError( "errors happened while running xgettext on %s\n%s" % (self.file, errors)) Loading @@ -101,14 +100,11 @@ class TranslatableFile(object): # Print warnings command.stdout.write(errors) if msgs: # Write/append messages to pot file potfile = os.path.join(self.locale_dir, '%s.pot' % str(domain)) if is_templatized: old = '#: ' + work_file[2:] new = '#: ' + orig_file[2:] msgs = msgs.replace(old, new) write_pot_file(potfile, msgs) if is_templatized: os.unlink(work_file) Loading Loading @@ -236,21 +232,21 @@ class Command(NoArgsCommand): settings.configure(USE_I18N = True) self.invoked_for_django = False self.locale_paths = [] self.default_locale_path = None if os.path.isdir(os.path.join('conf', 'locale')): self.locale_paths = [os.path.abspath(os.path.join('conf', 'locale'))] self.default_locale_path = self.locale_paths[0] localedir = os.path.abspath(os.path.join('conf', 'locale')) self.invoked_for_django = True # Ignoring all contrib apps self.ignore_patterns += ['contrib/*'] elif os.path.isdir('locale'): localedir = os.path.abspath('locale') else: self.locale_paths.extend(list(settings.LOCALE_PATHS)) # Allow to run makemessages inside an app dir if os.path.isdir('locale'): self.locale_paths.append(os.path.abspath('locale')) if self.locale_paths: self.default_locale_path = self.locale_paths[0] raise CommandError("This script should be run from the Django Git " "tree or your project or app tree. If you did indeed run it " "from the Git checkout or your project or application, " "maybe you are just missing the conf/locale (in the django " "tree) or locale (for project and application) directory? It " "is not created automatically, you have to create it by hand " "if you want to enable i18n for your project or application.") # We require gettext version 0.15 or newer. output, errors, status = _popen('xgettext --version') Loading @@ -265,25 +261,24 @@ class Command(NoArgsCommand): "gettext 0.15 or newer. You are using version %s, please " "upgrade your gettext toolset." % match.group()) try: potfiles = self.build_potfiles() potfile = self.build_pot_file(localedir) # Build po files for each selected locale locales = [] if locale is not None: locales = locale.split(',') if not isinstance(locale, list) else locale locales += locale.split(',') if not isinstance(locale, list) else locale elif process_all: locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path)) locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir)) locales = [os.path.basename(l) for l in locale_dirs] try: for locale in locales: if self.verbosity > 0: self.stdout.write("processing locale %s\n" % locale) for potfile in potfiles: self.write_po_file(potfile, locale) finally: if not self.keep_pot: self.remove_potfiles() if not self.keep_pot and os.path.exists(potfile): os.unlink(potfile) def build_pot_file(self, localedir): file_list = self.find_files(".") Loading @@ -297,41 +292,9 @@ class Command(NoArgsCommand): f.process(self, potfile, self.domain, self.keep_pot) return potfile def build_potfiles(self): """Build pot files and apply msguniq to them""" file_list = self.find_files(".") self.remove_potfiles() for f in file_list: f.process(self, self.domain) potfiles = [] for path in self.locale_paths: potfile = os.path.join(path, '%s.pot' % str(self.domain)) if not os.path.exists(potfile): continue msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' % (self.wrap, self.location, potfile)) if errors: if status != STATUS_OK: raise CommandError( "errors happened while running msguniq\n%s" % errors) elif self.verbosity > 0: self.stdout.write(errors) with open(potfile, 'w') as fp: fp.write(msgs) potfiles.append(potfile) return potfiles def remove_potfiles(self): for path in self.locale_paths: pot_path = os.path.join(path, '%s.pot' % str(self.domain)) if os.path.exists(pot_path): os.unlink(pot_path) def find_files(self, root): """ Helper function to get all files in the given root. Also check that there is a matching locale dir for each file. Helper method to get all files in the given root. """ def is_ignored(path, ignore_patterns): Loading @@ -352,26 +315,12 @@ class Command(NoArgsCommand): dirnames.remove(dirname) if self.verbosity > 1: self.stdout.write('ignoring directory %s\n' % dirname) elif dirname == 'locale': dirnames.remove(dirname) self.locale_paths.insert(0, os.path.join(os.path.abspath(dirpath), dirname)) for filename in filenames: file_path = os.path.normpath(os.path.join(dirpath, filename)) if is_ignored(file_path, self.ignore_patterns): if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), self.ignore_patterns): if self.verbosity > 1: self.stdout.write('ignoring file %s in %s\n' % (filename, dirpath)) else: locale_dir = None for path in self.locale_paths: if os.path.abspath(dirpath).startswith(os.path.dirname(path)): locale_dir = path break if not locale_dir: locale_dir = self.default_locale_path if not locale_dir: raise CommandError( "Unable to find a locale path to store translations for file %s" % file_path) all_files.append(TranslatableFile(dirpath, filename, locale_dir)) all_files.append(TranslatableFile(dirpath, filename)) return sorted(all_files) def write_po_file(self, potfile, locale): Loading @@ -379,8 +328,16 @@ class Command(NoArgsCommand): Creates or updates the PO file for self.domain and :param locale:. Uses contents of the existing :param potfile:. Uses msgmerge, and msgattrib GNU gettext utilities. Uses mguniq, msgmerge, and msgattrib GNU gettext utilities. """ msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' % (self.wrap, self.location, potfile)) if errors: if status != STATUS_OK: raise CommandError( "errors happened while running msguniq\n%s" % errors) elif self.verbosity > 0: self.stdout.write(errors) basedir = os.path.join(os.path.dirname(potfile), locale, 'LC_MESSAGES') if not os.path.isdir(basedir): Loading @@ -388,6 +345,8 @@ class Command(NoArgsCommand): pofile = os.path.join(basedir, '%s.po' % str(self.domain)) if os.path.exists(pofile): with open(potfile, 'w') as fp: fp.write(msgs) msgs, errors, status = _popen('msgmerge %s %s -q "%s" "%s"' % (self.wrap, self.location, pofile, potfile)) if errors: Loading @@ -396,9 +355,7 @@ class Command(NoArgsCommand): "errors happened while running msgmerge\n%s" % errors) elif self.verbosity > 0: self.stdout.write(errors) else: msgs = open(potfile, 'r').read() if not self.invoked_for_django: elif not self.invoked_for_django: msgs = self.copy_plural_forms(msgs, locale) msgs = msgs.replace( "#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % self.domain, "") Loading
docs/man/django-admin.1 +1 −2 Original line number Diff line number Diff line Loading @@ -193,8 +193,7 @@ Ignore files or directories matching this glob-style pattern. Use multiple times to ignore more (makemessages command). .TP .I \-\-no\-default\-ignore Don't ignore the common private glob-style patterns 'CVS', '.*', '*~' and '*.pyc' (makemessages command). Don't ignore the common private glob-style patterns 'CVS', '.*' and '*~' (makemessages command). .TP .I \-\-no\-wrap Don't break long message lines into several lines (makemessages command). Loading
docs/ref/django-admin.txt +2 −2 Original line number Diff line number Diff line Loading @@ -472,7 +472,7 @@ Example usage:: Use the ``--ignore`` or ``-i`` option to ignore files or directories matching the given :mod:`glob`-style pattern. Use multiple times to ignore more. These patterns are used by default: ``'CVS'``, ``'.*'``, ``'*~'``, ``'*.pyc'`` These patterns are used by default: ``'CVS'``, ``'.*'``, ``'*~'`` Example usage:: Loading @@ -499,7 +499,7 @@ for technically skilled translators to understand each message's context. .. versionadded:: 1.6 Use the ``--keep-pot`` option to prevent django from deleting the temporary .pot files it generates before creating the .po file. This is useful for .pot file it generates before creating the .po file. This is useful for debugging errors which may prevent the final language files from being created. runfcgi [options] Loading
docs/topics/i18n/translation.txt +16 −1 Original line number Diff line number Diff line Loading @@ -1543,9 +1543,24 @@ All message file repositories are structured the same way. They are: * ``$PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)`` To create message files, you use the :djadmin:`django-admin.py makemessages <makemessages>` tool. And you use :djadmin:`django-admin.py compilemessages <compilemessages>` tool. You only need to be in the same directory where the ``locale/`` directory is located. And you use :djadmin:`django-admin.py compilemessages <compilemessages>` to produce the binary ``.mo`` files that are used by ``gettext``. You can also run :djadmin:`django-admin.py compilemessages --settings=path.to.settings <compilemessages>` to make the compiler process all the directories in your :setting:`LOCALE_PATHS` setting. Finally, you should give some thought to the structure of your translation files. If your applications need to be delivered to other users and will be used in other projects, you might want to use app-specific translations. But using app-specific translations and project-specific translations could produce weird problems with :djadmin:`makemessages`: it will traverse all directories below the current path and so might put message IDs into a unified, common message file for the current project that are already in application message files. The easiest way out is to store applications that are not part of the project (and so carry their own translations) outside the project tree. That way, :djadmin:`django-admin.py makemessages <makemessages>`, when ran on a project level will only extract strings that are connected to your explicit project and not strings that are distributed independently.
tests/regressiontests/i18n/commands/extraction.py +0 −44 Original line number Diff line number Diff line Loading @@ -5,13 +5,10 @@ import os import re import shutil from django.conf import settings from django.core import management from django.test import SimpleTestCase from django.test.utils import override_settings from django.utils.encoding import force_text from django.utils._os import upath from django.utils import six from django.utils.six import StringIO Loading Loading @@ -355,44 +352,3 @@ class MultipleLocaleExtractionTests(ExtractorTests): management.call_command('makemessages', locale='pt,de,ch', verbosity=0) self.assertTrue(os.path.exists(self.PO_FILE_PT)) self.assertTrue(os.path.exists(self.PO_FILE_DE)) class CustomLayoutExtractionTests(ExtractorTests): def setUp(self): self._cwd = os.getcwd() self.test_dir = os.path.join(os.path.dirname(upath(__file__)), 'project_dir') def test_no_locale_raises(self): os.chdir(self.test_dir) with six.assertRaisesRegex(self, management.CommandError, "Unable to find a locale path to store translations for file"): management.call_command('makemessages', locale=LOCALE, verbosity=0) @override_settings( LOCALE_PATHS=(os.path.join(os.path.dirname(upath(__file__)), 'project_dir/project_locale'),) ) def test_project_locale_paths(self): """ Test that: * translations for app containing locale folder are stored in that folder * translations outside of that app are in LOCALE_PATHS[0] """ os.chdir(self.test_dir) self.addCleanup(shutil.rmtree, os.path.join(settings.LOCALE_PATHS[0], LOCALE)) self.addCleanup(shutil.rmtree, os.path.join(self.test_dir, 'app_with_locale/locale', LOCALE)) management.call_command('makemessages', locale=LOCALE, verbosity=0) project_de_locale = os.path.join( self.test_dir, 'project_locale/de/LC_MESSAGES/django.po',) app_de_locale = os.path.join( self.test_dir, 'app_with_locale/locale/de/LC_MESSAGES/django.po',) self.assertTrue(os.path.exists(project_de_locale)) self.assertTrue(os.path.exists(app_de_locale)) with open(project_de_locale, 'r') as fp: po_contents = force_text(fp.read()) self.assertMsgId('This app has no locale directory', po_contents) self.assertMsgId('This is a project-level string', po_contents) with open(app_de_locale, 'r') as fp: po_contents = force_text(fp.read()) self.assertMsgId('This app has a locale directory', po_contents)