Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Patch by Claude for #16084.

  • Loading branch information...
commit 2babab0bb351ff7a13fd23795f5e926a9bf95d22 1 parent b9c8bbf
@ramiro ramiro authored
View
139 django/core/management/commands/makemessages.py
@@ -19,25 +19,28 @@
@total_ordering
class TranslatableFile(object):
- def __init__(self, dirpath, file_name):
+ def __init__(self, dirpath, file_name, locale_dir):
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.dirpath == other.dirpath and self.file == other.file
+ return self.path == other.path
def __lt__(self, other):
- if self.dirpath == other.dirpath:
- return self.file < other.file
- return self.dirpath < other.dirpath
+ return self.path < other.path
- def process(self, command, potfile, domain, keep_pot=False):
+ @property
+ def path(self):
+ return os.path.join(self.dirpath, self.file)
+
+ def process(self, command, domain):
"""
- Extract translatable literals from self.file for :param domain:
- creating or updating the :param potfile: POT file.
+ Extract translatable literals from self.file for :param domain:,
+ creating or updating the POT file.
Uses the xgettext GNU gettext utility.
"""
@@ -91,8 +94,6 @@ def process(self, command, potfile, domain, keep_pot=False):
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))
@@ -100,11 +101,14 @@ def process(self, command, potfile, domain, keep_pot=False):
# 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)
@@ -232,21 +236,21 @@ def handle_noargs(self, *args, **options):
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')):
- localedir = os.path.abspath(os.path.join('conf', 'locale'))
+ self.locale_paths = [os.path.abspath(os.path.join('conf', 'locale'))]
+ self.default_locale_path = self.locale_paths[0]
self.invoked_for_django = True
# Ignoring all contrib apps
self.ignore_patterns += ['contrib/*']
- elif os.path.isdir('locale'):
- localedir = os.path.abspath('locale')
else:
- 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.")
+ 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]
# We require gettext version 0.15 or newer.
output, errors, status = _popen('xgettext --version')
@@ -261,24 +265,25 @@ def handle_noargs(self, *args, **options):
"gettext 0.15 or newer. You are using version %s, please "
"upgrade your gettext toolset." % match.group())
- potfile = self.build_pot_file(localedir)
+ try:
+ potfiles = self.build_potfiles()
- # Build po files for each selected locale
- locales = []
- if locale is not None:
- locales += locale.split(',') if not isinstance(locale, list) else locale
- elif process_all:
- locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir))
- locales = [os.path.basename(l) for l in locale_dirs]
+ # Build po files for each selected locale
+ locales = []
+ if locale is not None:
+ 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))
+ 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)
- self.write_po_file(potfile, locale)
+ for potfile in potfiles:
+ self.write_po_file(potfile, locale)
finally:
- if not self.keep_pot and os.path.exists(potfile):
- os.unlink(potfile)
+ if not self.keep_pot:
+ self.remove_potfiles()
def build_pot_file(self, localedir):
file_list = self.find_files(".")
@@ -292,9 +297,41 @@ def build_pot_file(self, localedir):
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 method to get all files in the given root.
+ Helper function to get all files in the given root. Also check that there
+ is a matching locale dir for each file.
"""
def is_ignored(path, ignore_patterns):
@@ -315,12 +352,26 @@ def is_ignored(path, ignore_patterns):
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:
- if is_ignored(os.path.normpath(os.path.join(dirpath, filename)), self.ignore_patterns):
+ file_path = os.path.normpath(os.path.join(dirpath, filename))
+ if is_ignored(file_path, self.ignore_patterns):
if self.verbosity > 1:
self.stdout.write('ignoring file %s in %s\n' % (filename, dirpath))
else:
- all_files.append(TranslatableFile(dirpath, filename))
+ 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))
return sorted(all_files)
def write_po_file(self, potfile, locale):
@@ -328,16 +379,8 @@ def write_po_file(self, potfile, locale):
Creates or updates the PO file for self.domain and :param locale:.
Uses contents of the existing :param potfile:.
- Uses mguniq, msgmerge, and msgattrib GNU gettext utilities.
+ Uses 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):
@@ -345,8 +388,6 @@ def write_po_file(self, potfile, locale):
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:
@@ -355,8 +396,10 @@ def write_po_file(self, potfile, locale):
"errors happened while running msgmerge\n%s" % errors)
elif self.verbosity > 0:
self.stdout.write(errors)
- elif not self.invoked_for_django:
- msgs = self.copy_plural_forms(msgs, locale)
+ else:
+ msgs = open(potfile, 'r').read()
+ if not self.invoked_for_django:
+ msgs = self.copy_plural_forms(msgs, locale)
msgs = msgs.replace(
"#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % self.domain, "")
with open(pofile, 'w') as fp:
View
3  docs/man/django-admin.1
@@ -193,7 +193,8 @@ 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 '*~' (makemessages command).
+Don't ignore the common private glob-style patterns 'CVS', '.*', '*~' and '*.pyc'
+(makemessages command).
.TP
.I \-\-no\-wrap
Don't break long message lines into several lines (makemessages command).
View
4 docs/ref/django-admin.txt
@@ -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'``, ``'.*'``, ``'*~'``
+These patterns are used by default: ``'CVS'``, ``'.*'``, ``'*~'``, ``'*.pyc'``
Example usage::
@@ -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 file it generates before creating the .po file. This is useful for
+.pot files 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]
View
17 docs/topics/i18n/translation.txt
@@ -1543,24 +1543,9 @@ 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. You only need to be in the same directory where the ``locale/`` directory
-is located. And you use :djadmin:`django-admin.py compilemessages <compilemessages>`
+tool. 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.
View
44 tests/regressiontests/i18n/commands/extraction.py
@@ -5,10 +5,13 @@
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
@@ -352,3 +355,44 @@ def test_comma_separated_locales(self):
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)
View
3  tests/regressiontests/i18n/commands/project_dir/__init__.py
@@ -0,0 +1,3 @@
+from django.utils.translation import ugettext as _
+
+string = _("This is a project-level string")
View
4 tests/regressiontests/i18n/commands/project_dir/app_no_locale/models.py
@@ -0,0 +1,4 @@
+from django.utils.translation import ugettext as _
+
+string = _("This app has no locale directory")
+
View
4 tests/regressiontests/i18n/commands/project_dir/app_with_locale/models.py
@@ -0,0 +1,4 @@
+from django.utils.translation import ugettext as _
+
+string = _("This app has a locale directory")
+
View
2  tests/regressiontests/i18n/tests.py
@@ -33,7 +33,7 @@
JavascriptExtractorTests, IgnoredExtractorTests, SymlinkExtractorTests,
CopyPluralFormsExtractorTests, NoWrapExtractorTests,
NoLocationExtractorTests, KeepPotFileExtractorTests,
- MultipleLocaleExtractionTests)
+ MultipleLocaleExtractionTests, CustomLayoutExtractionTests)
if can_run_compilation_tests:
from .commands.compilation import (PoFileTests, PoFileContentsTests,
PercentRenderingTests, MultipleLocaleCompilationTests)
Please sign in to comment.
Something went wrong with that request. Please try again.