Fixed #7084 -- Fixed the "makemessages" command to handle line endings
[django.git] / django / core / management / commands / makemessages.py
blobc987dec92629f3b9a8a7c066809647f19411f6f8
1 import re
2 import os
3 import sys
4 from itertools import dropwhile
5 from optparse import make_option
6 from django.core.management.base import CommandError, BaseCommand
8 try:
9 set
10 except NameError:
11 from sets import Set as set # For Python 2.3
13 pythonize_re = re.compile(r'\n\s*//')
15 def handle_extensions(extensions=('html',)):
16 """
17 organizes multiple extensions that are separated with commas or passed by
18 using --extension/-e multiple times.
20 for example: running 'django-admin makemessages -e js,txt -e xhtml -a'
21 would result in a extension list: ['.js', '.txt', '.xhtml']
23 >>> handle_extensions(['.html', 'html,js,py,py,py,.py', 'py,.py'])
24 ['.html', '.js']
25 >>> handle_extensions(['.html, txt,.tpl'])
26 ['.html', '.tpl', '.txt']
27 """
28 ext_list = []
29 for ext in extensions:
30 ext_list.extend(ext.replace(' ','').split(','))
31 for i, ext in enumerate(ext_list):
32 if not ext.startswith('.'):
33 ext_list[i] = '.%s' % ext_list[i]
35 # we don't want *.py files here because of the way non-*.py files
36 # are handled in make_messages() (they are copied to file.ext.py files to
37 # trick xgettext to parse them as Python files)
38 return set([x for x in ext_list if x != '.py'])
40 def make_messages(locale=None, domain='django', verbosity='1', all=False, extensions=None):
41 """
42 Uses the locale directory from the Django SVN tree or an application/
43 project to process all
44 """
45 # Need to ensure that the i18n framework is enabled
46 from django.conf import settings
47 if settings.configured:
48 settings.USE_I18N = True
49 else:
50 settings.configure(USE_I18N = True)
52 from django.utils.translation import templatize
54 if os.path.isdir(os.path.join('conf', 'locale')):
55 localedir = os.path.abspath(os.path.join('conf', 'locale'))
56 elif os.path.isdir('locale'):
57 localedir = os.path.abspath('locale')
58 else:
59 raise CommandError("This script should be run from the Django SVN tree or your project or app tree. If you did indeed run it from the SVN 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.")
61 if domain not in ('django', 'djangojs'):
62 raise CommandError("currently makemessages only supports domains 'django' and 'djangojs'")
64 if (locale is None and not all) or domain is None:
65 # backwards compatible error message
66 if not sys.argv[0].endswith("make-messages.py"):
67 message = "Type '%s help %s' for usage.\n" % (os.path.basename(sys.argv[0]), sys.argv[1])
68 else:
69 message = "usage: make-messages.py -l <language>\n or: make-messages.py -a\n"
70 raise CommandError(message)
72 languages = []
73 if locale is not None:
74 languages.append(locale)
75 elif all:
76 languages = [el for el in os.listdir(localedir) if not el.startswith('.')]
78 for locale in languages:
79 if verbosity > 0:
80 print "processing language", locale
81 basedir = os.path.join(localedir, locale, 'LC_MESSAGES')
82 if not os.path.isdir(basedir):
83 os.makedirs(basedir)
85 pofile = os.path.join(basedir, '%s.po' % domain)
86 potfile = os.path.join(basedir, '%s.pot' % domain)
88 if os.path.exists(potfile):
89 os.unlink(potfile)
91 all_files = []
92 for (dirpath, dirnames, filenames) in os.walk("."):
93 all_files.extend([(dirpath, f) for f in filenames])
94 all_files.sort()
95 for dirpath, file in all_files:
96 file_base, file_ext = os.path.splitext(file)
97 if domain == 'djangojs' and file_ext == '.js':
98 if verbosity > 1:
99 sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
100 src = open(os.path.join(dirpath, file), "rU").read()
101 src = pythonize_re.sub('\n#', src)
102 thefile = '%s.py' % file
103 open(os.path.join(dirpath, thefile), "w").write(src)
104 cmd = 'xgettext -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (domain, os.path.join(dirpath, thefile))
105 (stdin, stdout, stderr) = os.popen3(cmd, 't')
106 msgs = stdout.read()
107 errors = stderr.read()
108 if errors:
109 raise CommandError("errors happened while running xgettext on %s\n%s" % (file, errors))
110 old = '#: '+os.path.join(dirpath, thefile)[2:]
111 new = '#: '+os.path.join(dirpath, file)[2:]
112 msgs = msgs.replace(old, new)
113 if os.path.exists(potfile):
114 # Strip the header
115 msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
116 else:
117 msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
118 if msgs:
119 open(potfile, 'ab').write(msgs)
120 os.unlink(os.path.join(dirpath, thefile))
121 elif domain == 'django' and (file_ext == '.py' or file_ext in extensions):
122 thefile = file
123 if file_ext in extensions:
124 src = open(os.path.join(dirpath, file), "rU").read()
125 thefile = '%s.py' % file
126 open(os.path.join(dirpath, thefile), "w").write(templatize(src))
127 if verbosity > 1:
128 sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
129 cmd = 'xgettext -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (
130 domain, os.path.join(dirpath, thefile))
131 (stdin, stdout, stderr) = os.popen3(cmd, 't')
132 msgs = stdout.read()
133 errors = stderr.read()
134 if errors:
135 raise CommandError("errors happened while running xgettext on %s\n%s" % (file, errors))
136 if thefile != file:
137 old = '#: '+os.path.join(dirpath, thefile)[2:]
138 new = '#: '+os.path.join(dirpath, file)[2:]
139 msgs = msgs.replace(old, new)
140 if os.path.exists(potfile):
141 # Strip the header
142 msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
143 else:
144 msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
145 if msgs:
146 open(potfile, 'ab').write(msgs)
147 if thefile != file:
148 os.unlink(os.path.join(dirpath, thefile))
150 if os.path.exists(potfile):
151 (stdin, stdout, stderr) = os.popen3('msguniq --to-code=utf-8 "%s"' % potfile, 't')
152 msgs = stdout.read()
153 errors = stderr.read()
154 if errors:
155 raise CommandError("errors happened while running msguniq\n%s" % errors)
156 open(potfile, 'w').write(msgs)
157 if os.path.exists(pofile):
158 (stdin, stdout, stderr) = os.popen3('msgmerge -q "%s" "%s"' % (pofile, potfile), 't')
159 msgs = stdout.read()
160 errors = stderr.read()
161 if errors:
162 raise CommandError("errors happened while running msgmerge\n%s" % errors)
163 open(pofile, 'wb').write(msgs)
164 os.unlink(potfile)
167 class Command(BaseCommand):
168 option_list = BaseCommand.option_list + (
169 make_option('--locale', '-l', default=None, dest='locale',
170 help='Creates or updates the message files only for the given locale (e.g. pt_BR).'),
171 make_option('--domain', '-d', default='django', dest='domain',
172 help='The domain of the message files (default: "django").'),
173 make_option('--verbosity', '-v', action='store', dest='verbosity',
174 default='1', type='choice', choices=['0', '1', '2'],
175 help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
176 make_option('--all', '-a', action='store_true', dest='all',
177 default=False, help='Reexamines all source code and templates for new translation strings and updates all message files for all available languages.'),
178 make_option('--extension', '-e', dest='extensions',
179 help='The file extension(s) to examine (default: ".html", separate multiple extensions with commas, or use -e multiple times)',
180 action='append'),
182 help = "Runs over the entire source tree of the current directory and pulls out all strings marked for translation. It creates (or updates) a message file in the conf/locale (in the django tree) or locale (for project and application) directory."
184 requires_model_validation = False
185 can_import_settings = False
187 def handle(self, *args, **options):
188 if len(args) != 0:
189 raise CommandError("Command doesn't accept any arguments")
191 locale = options.get('locale')
192 domain = options.get('domain')
193 verbosity = int(options.get('verbosity'))
194 process_all = options.get('all')
195 extensions = options.get('extensions') or ['html']
197 if domain == 'djangojs':
198 extensions = []
199 else:
200 extensions = handle_extensions(extensions)
202 if '.js' in extensions:
203 raise CommandError("JavaScript files should be examined by using the special 'djangojs' domain only.")
205 make_messages(locale, domain, verbosity, process_all, extensions)