makemessages.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import codecs
  2. from hashlib import md5
  3. import os
  4. import re
  5. from path import Path
  6. from django.core.management.commands.makemessages import Command as BaseCommand
  7. from django.utils.text import smart_split
  8. HBS_EXPRESSION = re.compile(r'({{{(.*?)}}})|({{(.*?)}})')
  9. HELPERS = {
  10. 'gettext': 1,
  11. 'ngettext': 3,
  12. 'gettext_noop': 1,
  13. 'pgettext': 2,
  14. 'npgettext': 4
  15. }
  16. class HandlebarsTemplate(object):
  17. def __init__(self, content):
  18. self.content = content
  19. def get_converted_content(self):
  20. stripped_content = self.strip_expressions(self.content)
  21. stripped_content = self.strip_non_expressions(stripped_content)
  22. replaced_content = self.replace_expressions(stripped_content)
  23. return replaced_content
  24. def strip_expressions(self, content):
  25. def replace_expression(matchobj):
  26. trimmed_expression = matchobj.group(0).lstrip('{').rstrip('}')
  27. trimmed_expression = trimmed_expression.strip()
  28. expression_words = trimmed_expression.split()
  29. if expression_words[0] in HELPERS:
  30. return matchobj.group(0)
  31. else:
  32. return ' ' * len(matchobj.group(0))
  33. return HBS_EXPRESSION.sub(replace_expression, self.content)
  34. def strip_non_expressions(self, content):
  35. stripped = u''
  36. cursor = 0
  37. for expression in HBS_EXPRESSION.finditer(content):
  38. position = content.find(expression.group(0), cursor)
  39. content_slice = content[cursor:position]
  40. if content_slice:
  41. slice_lines = len(content_slice.splitlines())
  42. if slice_lines:
  43. stripped += '\n' * (slice_lines - 1)
  44. stripped += expression.group(0)
  45. cursor = position + len(expression.group(0))
  46. return stripped
  47. def replace_expressions(self, content):
  48. def replace_expression(matchobj):
  49. trimmed_expression = matchobj.group(0).lstrip('{').rstrip('}')
  50. trimmed_expression = trimmed_expression.strip()
  51. expression_bits = [b for b in smart_split(trimmed_expression)]
  52. function = expression_bits[0]
  53. args = expression_bits[1:HELPERS[function] + 1]
  54. return '%s(%s);' % (function, ', '.join(args))
  55. return HBS_EXPRESSION.sub(replace_expression, content)
  56. class HandlebasFile(object):
  57. def __init__(self, hbs_path):
  58. self.hbs_path = hbs_path
  59. self.path_suffix = self.make_js_path_suffix(hbs_path)
  60. self.js_path = self.make_js_path(hbs_path, self.path_suffix)
  61. self.make_js_file(self.hbs_path, self.js_path)
  62. def make_js_path_suffix(self, hbs_path):
  63. return '%s.tmp.js' % md5(hbs_path).hexdigest()[:8]
  64. def make_js_path(self, hbs_path, path_suffix):
  65. return Path('%s.%s' % (unicode(hbs_path), path_suffix))
  66. def make_js_file(self, hbs_path, js_path):
  67. file_content = u''
  68. with codecs.open(hbs_path, encoding='utf-8', mode="r") as hbs_file:
  69. file_content = hbs_file.read()
  70. js_file = codecs.open(js_path, encoding='utf-8', mode='w')
  71. js_file.write(HandlebarsTemplate(file_content).get_converted_content())
  72. js_file.close()
  73. def delete(self):
  74. if self.js_path.exists() and self.js_path.isfile():
  75. self.js_path.unlink()
  76. class Command(BaseCommand):
  77. help = ("Runs over the entire source tree of the current directory and "
  78. "pulls out all strings marked for translation. It creates (or updates) a message "
  79. "file in the conf/locale (in the django tree) or locale (for projects and "
  80. "applications) directory.\n\nIf command is executed for JavaScript files, it "
  81. "also pulls strings from Misago Handlebars.js files.\n\nYou must run this "
  82. "command with one of either the --locale, --exclude or --all options.")
  83. JS_TEMPLATES = ('.hbs', '.handlebars')
  84. def handle(self, *args, **options):
  85. locales = options.get('locale')
  86. self.domain = options.get('domain')
  87. subdirs = [unicode(d.basename()) for d in Path(os.getcwd()).dirs()]
  88. use_subroutines = 'locale' in subdirs and self.domain == 'djangojs'
  89. tmp_js_files = []
  90. if use_subroutines:
  91. # fake js files from templates
  92. tmp_js_files = self.prepare_tmp_js_files();
  93. super(Command, self).handle(*args, **options)
  94. if use_subroutines:
  95. # cleanup everything
  96. self.cleanup_tmp_js_templates(tmp_js_files);
  97. self.cleanup_po_files(locales, tmp_js_files);
  98. def prepare_tmp_js_files(self):
  99. files = []
  100. for hbs_file in Path(os.getcwd()).walkfiles('*.hbs'):
  101. files.append(HandlebasFile(hbs_file))
  102. return files
  103. def cleanup_po_files(self, locales, tmp_js_files):
  104. strip_tokens = [js_file.path_suffix for js_file in tmp_js_files]
  105. for po_file in Path(os.getcwd()).walkfiles('djangojs.po'):
  106. if not locales or po_file.splitall()[-3] in locales:
  107. self.cleanup_po_file(po_file, strip_tokens)
  108. def cleanup_po_file(self, po_path, strip_tokens):
  109. file_content = u''
  110. with codecs.open(po_path, encoding='utf-8', mode="r") as po_file:
  111. file_content = po_file.read()
  112. for token in strip_tokens:
  113. file_content = file_content.replace(token, '')
  114. po_file = codecs.open(po_path, encoding='utf-8', mode='w')
  115. po_file.write(file_content)
  116. po_file.close()
  117. def cleanup_tmp_js_templates(self, tmp_js_files):
  118. for js_file in tmp_js_files:
  119. js_file.delete()