compilemessages.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from __future__ import unicode_literals
  2. import codecs
  3. import glob
  4. import os
  5. from optparse import make_option
  6. from django.core.management.base import BaseCommand, CommandError
  7. from django.core.management.utils import find_command, popen_wrapper
  8. from django.utils._os import npath, upath
  9. def has_bom(fn):
  10. with open(fn, 'rb') as f:
  11. sample = f.read(4)
  12. return sample[:3] == b'\xef\xbb\xbf' or \
  13. sample.startswith(codecs.BOM_UTF16_LE) or \
  14. sample.startswith(codecs.BOM_UTF16_BE)
  15. def is_writable(path):
  16. # Known side effect: updating file access/modified time to current time if
  17. # it is writable.
  18. try:
  19. with open(path, 'a'):
  20. os.utime(path, None)
  21. except (IOError, OSError):
  22. return False
  23. return True
  24. class Command(BaseCommand):
  25. option_list = BaseCommand.option_list + (
  26. make_option('--locale', '-l', dest='locale', action='append', default=[],
  27. help='Locale(s) to process (e.g. de_AT). Default is to process all. Can be '
  28. 'used multiple times.'),
  29. make_option('--exclude', '-x', dest='exclude', action='append', default=[],
  30. help='Locales to exclude. Default is none. Can be used multiple times.'),
  31. )
  32. help = 'Compiles .po files to .mo files for use with builtin gettext support.'
  33. requires_system_checks = False
  34. leave_locale_alone = True
  35. program = 'msgfmt'
  36. program_options = ['--check-format']
  37. def handle(self, **options):
  38. locale = options.get('locale')
  39. exclude = options.get('exclude')
  40. self.verbosity = int(options.get('verbosity'))
  41. if find_command(self.program) is None:
  42. raise CommandError("Can't find %s. Make sure you have GNU gettext "
  43. "tools 0.15 or newer installed." % self.program)
  44. basedirs = [os.path.join('conf', 'locale'), 'locale']
  45. if os.environ.get('DJANGO_SETTINGS_MODULE'):
  46. from django.conf import settings
  47. basedirs.extend([upath(path) for path in settings.LOCALE_PATHS])
  48. # Gather existing directories.
  49. basedirs = set(map(os.path.abspath, filter(os.path.isdir, basedirs)))
  50. if not basedirs:
  51. raise CommandError("This script should be run from the Django Git "
  52. "checkout or your project or app tree, or with "
  53. "the settings module specified.")
  54. # Build locale list
  55. all_locales = []
  56. for basedir in basedirs:
  57. locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % basedir))
  58. all_locales.extend(map(os.path.basename, locale_dirs))
  59. # Account for excluded locales
  60. locales = locale or all_locales
  61. locales = set(locales) - set(exclude)
  62. for basedir in basedirs:
  63. if locales:
  64. dirs = [os.path.join(basedir, l, 'LC_MESSAGES') for l in locales]
  65. else:
  66. dirs = [basedir]
  67. locations = []
  68. for ldir in dirs:
  69. for dirpath, dirnames, filenames in os.walk(ldir):
  70. locations.extend((dirpath, f) for f in filenames if f.endswith('.po'))
  71. if locations:
  72. self.compile_messages(locations)
  73. def compile_messages(self, locations):
  74. """
  75. Locations is a list of tuples: [(directory, file), ...]
  76. """
  77. for i, (dirpath, f) in enumerate(locations):
  78. if self.verbosity > 0:
  79. self.stdout.write('processing file %s in %s\n' % (f, dirpath))
  80. po_path = os.path.join(dirpath, f)
  81. if has_bom(po_path):
  82. raise CommandError("The %s file has a BOM (Byte Order Mark). "
  83. "Django only supports .po files encoded in "
  84. "UTF-8 and without any BOM." % po_path)
  85. base_path = os.path.splitext(po_path)[0]
  86. # Check writability on first location
  87. if i == 0 and not is_writable(npath(base_path + '.mo')):
  88. self.stderr.write("The po files under %s are in a seemingly not writable location. "
  89. "mo files will not be updated/created." % dirpath)
  90. return
  91. args = [self.program] + self.program_options + ['-o',
  92. npath(base_path + '.mo'), npath(base_path + '.po')]
  93. output, errors, status = popen_wrapper(args)
  94. if status:
  95. if errors:
  96. msg = "Execution of %s failed: %s" % (self.program, errors)
  97. else:
  98. msg = "Execution of %s failed" % self.program
  99. raise CommandError(msg)