Advertisement
Guest User

Untitled

a guest
Feb 22nd, 2017
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.91 KB | None | 0 0
  1. #!/usr/bin/python3
  2.  
  3. # Note: This script has to be in the same directory with sync_po.py (.PO synchronizer).
  4.  
  5. """
  6. Made for combining Django's JavaScript Catalog (translation) implementation with AngularJS templates.
  7.  
  8. Code (AngularJS):
  9. ---------------
  10. .filter('translate', function() { return function(input, params) { return params != null ? interpolate(gettext(input), params, params.constructor == Object) : gettext(input) } })
  11. .directive('translate', function($compile) {
  12. return {
  13. compile: function(element) {
  14. var html = element.html().split('<br plural="">');
  15. return function (scope, element, attrs) {
  16. function c(h){
  17. element.html(h);
  18. $compile(element.contents())(scope);
  19. }
  20. if (attrs.translate != '') {
  21. scope.$watch(function () { return scope.$eval(attrs.translate) }, function (val) {
  22. var p = html[2];
  23. html[2] = ngettext(html[0], html[1], attrs.add !== undefined ? val + attrs.add : attrs.subtract !== undefined ? val - attrs.subtract : val);
  24. if (p != html[2]) c(html[2]);
  25. });
  26. } else c(gettext(html[0]));
  27. }
  28. }
  29. }
  30. })
  31. ---------------
  32.  
  33. Usage in templates and equivalent commands in JS:
  34. {{ "Save" | translate }}
  35. -> gettext("Save")
  36.  
  37. {{ "May" | translate : null : 'month name' }}
  38. -> pgettext('month name', "May")
  39.  
  40. {{ "Comments (%d)" | translate : [obj.comments.length] }}
  41. -> interpolate(gettext("Comments (%d)"), [obj.comments.length])
  42.  
  43. {{ "User(s) (%(user_count)d) are found %(distance)s away" | translate : {user_count: users.length, distance: obj.distance.value + obj.distance.unit} }}
  44. -> interpolate(gettext("User(s) (%(user_count)d) are found %(distance)s away"), {user_count: users.length, distance: obj.distance.value + obj.distance.unit}, true)
  45.  
  46. <a href="/friends/" translate="friends.length">You have {{ friends.length }} friend.<br plural>You have {{ friends.length }} friends.</a>
  47. -> ngettext("You have {{ friends.length }} friend.", "You have {{ friends.length }} friends.", friends.length)
  48. """
  49.  
  50. EXT = 'html'
  51. TAG = 'translate'
  52. MSGSTR_NUM = 2
  53.  
  54. from re import compile
  55. from html.parser import HTMLParser
  56.  
  57. prog = [compile(r'("|\')(.*?)\1'), compile(r'%(\(.*?\))?(s|d)')]
  58. class TemplateParser(HTMLParser):
  59. recording = 0
  60. f = ['', '']
  61. tmp = [None, '']
  62.  
  63. def __init__(self, *args, **kwargs):
  64. self.tag = kwargs.pop('tag')
  65. self.msgstr_num = kwargs.pop('msgstr_num')
  66. self.prog = compile(r'\{{2} *([^\s|}]+(?: +[^\s|}]+)*) *\| *'+self.tag+r'(?: *:(?:[^:]*): *(["\'])([^"\'}]*(?:(?!\2)["\'][^"\'}]*)*)\2)?[^}]*\}{2}')
  67. super().__init__()
  68.  
  69. def genc(self):
  70. return self.f[0][0]+':'+str(self.getpos()[0])
  71.  
  72. def addnew(self, s=''):
  73. return {'#': {':': [self.genc()]}, 'msgid': [s]}
  74.  
  75. def appendpos(self, p, s):
  76. for i in s:
  77. if p in i:
  78. return
  79. for i, _ in enumerate(s):
  80. if ' '+self.f[0][0] in s[i]:
  81. s[i] += ' '+p
  82. return
  83. s.append(p)
  84.  
  85. def repl(self, s):
  86. if self.f[0][1]:
  87. s = s.replace('{% verbatim %}', '').replace('{% endverbatim %}', '')
  88. return s.replace('"', '\\"').replace('\\', '\\\\')
  89.  
  90. def extr_filter(self, t):
  91. for e in self.prog.findall(t):
  92. for s in prog[0].findall(e[0]):
  93. r = self.repl(s[1])
  94. for i in self.data:
  95. if not i.get('msgid_plural', None) and ''.join(i['msgid']) == r and ''.join(i.get('msgctxt', [])) == e[2]:
  96. self.appendpos(self.genc(), i['#'][':'])
  97. return
  98. self.data.append(self.addnew(r))
  99. if prog[1].search(s[1]):
  100. self.data[-1]['#'][','] = ['javascript-format']
  101. if e[2] != '':
  102. self.data[-1]['msgctxt'] = [e[2]]
  103. self.data[-1]['msgstr'] = ['']
  104.  
  105. def handle_starttag(self, tag, attrs):
  106. s = self.get_starttag_text()
  107. self.extr_filter(s)
  108. if self.recording > 0:
  109. if s == '<br plural>':
  110. self.f[1] = 'msgid_plural'
  111. self.tmp[0][self.f[1]] = ['']
  112. return
  113. elif tag not in ('br', 'img', 'input', 'hr'):
  114. self.recording += 1
  115. self.tmp[0][self.f[1]][0] += self.repl(s)
  116. return
  117. for name, _ in attrs:
  118. if name == self.tag:
  119. self.tmp[0] = self.addnew()
  120. self.f[1] = 'msgid'
  121. self.recording = 1
  122. break
  123.  
  124. def handle_data(self, data):
  125. self.tmp[1] += data
  126. if self.recording > 0:
  127. self.tmp[0][self.f[1]][0] += self.repl(data)
  128.  
  129. def handle_endtag(self, tag):
  130. if self.recording > 0:
  131. self.recording -= 1
  132. if self.recording == 0:
  133. for i in self.data:
  134. if ''.join(i['msgid']) == ''.join(self.tmp[0]['msgid']) and ''.join(i.get('msgid_plural', [])) == ''.join(self.tmp[0].get('msgid_plural', [])):
  135. self.appendpos(self.tmp[0]['#'][':'][0], i['#'][':'])
  136. self.tmp[0] = None
  137. return
  138. if 'msgid_plural' in self.tmp[0]:
  139. self.tmp[0]['msgstr'] = {}
  140. for i in range(0, self.msgstr_num):
  141. self.tmp[0]['msgstr'][str(i)] = ['']
  142. else:
  143. self.tmp[0]['msgstr'] = ['']
  144. self.data.append(self.tmp[0].copy())
  145. self.tmp[0] = None
  146. else:
  147. self.tmp[0][self.f[1]][0] += '</'+tag+'>'
  148. self.extr_filter(self.tmp[1])
  149. self.tmp[1] = ''
  150.  
  151. def handle_entityref(self, name):
  152. self.tmp[1] += '&'+name+';'
  153. if self.recording > 0:
  154. self.tmp[0][self.f[1]][0] += '&'+name+';'
  155.  
  156. def main(target_dir, output_file, source_file=None, overwrite=False, tag=TAG, ext=EXT, msgstr_num=MSGSTR_NUM):
  157. import os
  158. import sync_po
  159.  
  160. parser = TemplateParser(tag=tag, msgstr_num=msgstr_num)
  161.  
  162. if not overwrite and os.path.exists(output_file):
  163. with open(output_file, 'r') as f:
  164. parser.data = sync_po.extract_strings(f.readlines())
  165. else:
  166. parser.data = []
  167.  
  168. ext = '.'+ext
  169. for subdir, _, files in os.walk(target_dir):
  170. for file in files:
  171. filepath = subdir + os.sep + file
  172. if filepath.endswith(ext):
  173. with open(filepath, 'r') as f:
  174. parser.f[0] = (filepath if filepath[:2] != './' else filepath[2:], os.sep+'static'+os.sep not in filepath)
  175. parser.feed(f.read())
  176. parser.reset()
  177.  
  178. if source_file and not os.path.samefile(output_file, source_file.name):
  179. with source_file as f:
  180. sync_po.main(sync_po.extract_strings(f.readlines()), parser.data)
  181.  
  182. with open(output_file, 'w') as f:
  183. sync_po.output_strings(parser.data, f)
  184.  
  185. if __name__ == '__main__':
  186. import argparse
  187. parser = argparse.ArgumentParser(description="Generates .PO file from AngularJS templates (optionally with translations from other .PO file), or updates existing. By MikiSoft")
  188. parser.add_argument('target_dir', type=str)
  189. parser.add_argument('output_file', type=str)
  190. parser.add_argument('-s', dest='source_file', metavar="FILE", type=argparse.FileType('r'), help="source file")
  191. parser.add_argument('-o', dest='overwrite', action='store_true', help="don't update, overwrite output file if exists")
  192. parser.add_argument('-tag', type=str, default=TAG, help="default: "+TAG)
  193. parser.add_argument('-ext', type=str, default=EXT, help="default: "+EXT)
  194. parser.add_argument('-msgstr_num', metavar="N", type=int, default=MSGSTR_NUM, help="default: "+str(MSGSTR_NUM))
  195. args = parser.parse_args()
  196.  
  197. main(args.target_dir, args.output_file, args.source_file, args.overwrite, args.tag, args.ext, args.msgstr_num)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement