Forked mumble-django project from https://bitbucket.org/Svedrin/mumble-django
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

322 lines
14 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. * Copyright (C) 2010, Marco Bonetti <mbonetti@gmail.com>
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. * THE SOFTWARE.
  22. """
  23. from django.conf import settings
  24. from django.contrib.auth.decorators import user_passes_test
  25. from django.core.paginator import Paginator, InvalidPage
  26. from django.core.urlresolvers import reverse, resolve, Resolver404
  27. from django.http import Http404, HttpResponseRedirect, HttpResponse
  28. from django.shortcuts import render_to_response
  29. from django.utils.encoding import smart_unicode, force_unicode, iri_to_uri
  30. from django.utils.translation import ugettext_lazy as _
  31. from django.views.decorators.cache import never_cache
  32. from rosetta.polib import pofile
  33. from rosetta.poutil import find_pos, pagination_range
  34. from rosetta.conf import settings as rosetta_settings
  35. import re, os, rosetta, datetime, unicodedata
  36. from django.template import RequestContext
  37. try:
  38. resolve(settings.LOGIN_URL)
  39. except Resolver404:
  40. try:
  41. resolve('/admin/')
  42. except Resolver404:
  43. raise Exception('Rosetta cannot log you in!\nYou must define a LOGIN_URL in your settings if you don\'t run the Django admin site at a standard URL.')
  44. else:
  45. LOGIN_URL = '/admin/'
  46. else:
  47. LOGIN_URL = settings.LOGIN_URL
  48. def home(request):
  49. """
  50. Displays a list of messages to be translated
  51. """
  52. def fix_nls(in_,out_):
  53. """Fixes submitted translations by filtering carriage returns and pairing
  54. newlines at the begging and end of the translated string with the original
  55. """
  56. if 0 == len(in_) or 0 == len(out_):
  57. return out_
  58. if "\r" in out_ and "\r" not in in_:
  59. out_=out_.replace("\r",'')
  60. if "\n" == in_[0] and "\n" != out_[0]:
  61. out_ = "\n" + out_
  62. elif "\n" != in_[0] and "\n" == out_[0]:
  63. out_ = out_.lstrip()
  64. if "\n" == in_[-1] and "\n" != out_[-1]:
  65. out_ = out_ + "\n"
  66. elif "\n" != in_[-1] and "\n" == out_[-1]:
  67. out_ = out_.rstrip()
  68. return out_
  69. version = rosetta.get_version(True)
  70. if 'rosetta_i18n_fn' in request.session:
  71. rosetta_i18n_fn=request.session.get('rosetta_i18n_fn')
  72. rosetta_i18n_app = get_app_name(rosetta_i18n_fn)
  73. rosetta_i18n_pofile = request.session.get('rosetta_i18n_pofile')
  74. rosetta_i18n_lang_code = request.session['rosetta_i18n_lang_code']
  75. rosetta_i18n_lang_bidi = (rosetta_i18n_lang_code in settings.LANGUAGES_BIDI)
  76. rosetta_i18n_write = request.session.get('rosetta_i18n_write', True)
  77. if 'filter' in request.GET:
  78. if request.GET.get('filter') in ('untranslated', 'translated', 'fuzzy', 'all'):
  79. filter_ = request.GET.get('filter')
  80. request.session['rosetta_i18n_filter'] = filter_
  81. return HttpResponseRedirect(reverse('rosetta-home'))
  82. rosetta_i18n_filter = request.session.get('rosetta_i18n_filter', 'all')
  83. if '_next' in request.POST:
  84. rx=re.compile(r'^m_([0-9]+)')
  85. rx_plural=re.compile(r'^m_([0-9]+)_([0-9]+)')
  86. file_change = False
  87. for k in request.POST.keys():
  88. if rx_plural.match(k):
  89. id=int(rx_plural.match(k).groups()[0])
  90. idx=int(rx_plural.match(k).groups()[1])
  91. rosetta_i18n_pofile[id].msgstr_plural[str(idx)] = fix_nls(rosetta_i18n_pofile[id].msgid_plural[idx], request.POST.get(k))
  92. file_change = True
  93. elif rx.match(k):
  94. id=int(rx.match(k).groups()[0])
  95. rosetta_i18n_pofile[id].msgstr = fix_nls(rosetta_i18n_pofile[id].msgid, request.POST.get(k))
  96. file_change = True
  97. if file_change and 'fuzzy' in rosetta_i18n_pofile[id].flags and not request.POST.get('f_%d' %id, False):
  98. rosetta_i18n_pofile[id].flags.remove('fuzzy')
  99. elif file_change and 'fuzzy' not in rosetta_i18n_pofile[id].flags and request.POST.get('f_%d' %id, False):
  100. rosetta_i18n_pofile[id].flags.append('fuzzy')
  101. if file_change and rosetta_i18n_write:
  102. try:
  103. rosetta_i18n_pofile.metadata['Last-Translator'] = unicodedata.normalize('NFKD', u"%s %s <%s>" %(request.user.first_name,request.user.last_name,request.user.email)).encode('ascii', 'ignore')
  104. rosetta_i18n_pofile.metadata['X-Translated-Using'] = u"django-rosetta %s" % rosetta.get_version(False)
  105. rosetta_i18n_pofile.metadata['PO-Revision-Date'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M%z')
  106. except UnicodeDecodeError:
  107. pass
  108. try:
  109. rosetta_i18n_pofile.save()
  110. rosetta_i18n_pofile.save_as_mofile(rosetta_i18n_fn.replace('.po','.mo'))
  111. # Try auto-reloading via the WSGI daemon mode reload mechanism
  112. if rosetta_settings.WSGI_AUTO_RELOAD and \
  113. request.environ.has_key('mod_wsgi.process_group') and \
  114. request.environ.get('mod_wsgi.process_group',None) and \
  115. request.environ.has_key('SCRIPT_FILENAME') and \
  116. int(request.environ.get('mod_wsgi.script_reloading', '0')):
  117. try:
  118. os.utime(request.environ.get('SCRIPT_FILENAME'), None)
  119. except OSError:
  120. pass
  121. except:
  122. request.session['rosetta_i18n_write'] = False
  123. request.session['rosetta_i18n_pofile']=rosetta_i18n_pofile
  124. # Retain query arguments
  125. query_arg = ''
  126. if 'query' in request.REQUEST:
  127. query_arg = '?query=%s' %request.REQUEST.get('query')
  128. if 'page' in request.GET:
  129. if query_arg:
  130. query_arg = query_arg + '&'
  131. else:
  132. query_arg = '?'
  133. query_arg = query_arg + 'page=%d' % int(request.GET.get('page'))
  134. return HttpResponseRedirect(reverse('rosetta-home') + iri_to_uri(query_arg))
  135. rosetta_i18n_lang_name = _(request.session.get('rosetta_i18n_lang_name'))
  136. rosetta_i18n_lang_code = request.session.get('rosetta_i18n_lang_code')
  137. if 'query' in request.REQUEST and request.REQUEST.get('query','').strip():
  138. query=request.REQUEST.get('query').strip()
  139. rx=re.compile(query, re.IGNORECASE)
  140. paginator = Paginator([e for e in rosetta_i18n_pofile if rx.search(smart_unicode(e.msgstr)+smart_unicode(e.msgid)+u''.join([o[0] for o in e.occurrences]))], rosetta_settings.MESSAGES_PER_PAGE)
  141. else:
  142. if rosetta_i18n_filter == 'untranslated':
  143. paginator = Paginator(rosetta_i18n_pofile.untranslated_entries(), rosetta_settings.MESSAGES_PER_PAGE)
  144. elif rosetta_i18n_filter == 'translated':
  145. paginator = Paginator(rosetta_i18n_pofile.translated_entries(), rosetta_settings.MESSAGES_PER_PAGE)
  146. elif rosetta_i18n_filter == 'fuzzy':
  147. paginator = Paginator(rosetta_i18n_pofile.fuzzy_entries(), rosetta_settings.MESSAGES_PER_PAGE)
  148. else:
  149. paginator = Paginator([e for e in rosetta_i18n_pofile if not e.obsolete], rosetta_settings.MESSAGES_PER_PAGE)
  150. if 'page' in request.GET and int(request.GET.get('page')) <= paginator.num_pages and int(request.GET.get('page')) > 0:
  151. page = int(request.GET.get('page'))
  152. else:
  153. page = 1
  154. messages = paginator.page(page).object_list
  155. if rosetta_settings.MAIN_LANGUAGE and rosetta_settings.MAIN_LANGUAGE != rosetta_i18n_lang_code:
  156. main_language = None
  157. for language in settings.LANGUAGES:
  158. if language[0] == rosetta_settings.MAIN_LANGUAGE:
  159. main_language = _(language[1])
  160. break
  161. fl = ("/%s/" % rosetta_settings.MAIN_LANGUAGE).join(rosetta_i18n_fn.split("/%s/" % rosetta_i18n_lang_code))
  162. po = pofile(fl)
  163. main_messages = []
  164. for message in messages:
  165. message.main_lang = po.find(message.msgid).msgstr
  166. needs_pagination = paginator.num_pages > 1
  167. if needs_pagination:
  168. if paginator.num_pages >= 10:
  169. page_range = pagination_range(1, paginator.num_pages, page)
  170. else:
  171. page_range = range(1,1+paginator.num_pages)
  172. ADMIN_MEDIA_PREFIX = settings.ADMIN_MEDIA_PREFIX
  173. ENABLE_TRANSLATION_SUGGESTIONS = rosetta_settings.ENABLE_TRANSLATION_SUGGESTIONS
  174. return render_to_response('rosetta/pofile.html', locals(), context_instance=RequestContext(request))
  175. else:
  176. return list_languages(request)
  177. home=user_passes_test(lambda user:can_translate(user),LOGIN_URL)(home)
  178. home=never_cache(home)
  179. def download_file(request):
  180. import zipfile, os
  181. from StringIO import StringIO
  182. # original filename
  183. rosetta_i18n_fn=request.session.get('rosetta_i18n_fn', None)
  184. # in-session modified catalog
  185. rosetta_i18n_pofile = request.session.get('rosetta_i18n_pofile', None)
  186. # language code
  187. rosetta_i18n_lang_code = request.session.get('rosetta_i18n_lang_code', None)
  188. if not rosetta_i18n_lang_code or not rosetta_i18n_pofile or not rosetta_i18n_fn:
  189. return HttpResponseRedirect(reverse('rosetta-home'))
  190. try:
  191. if len(rosetta_i18n_fn.split('/')) >= 5:
  192. offered_fn = '_'.join(rosetta_i18n_fn.split('/')[-5:])
  193. else:
  194. offered_fn = rosetta_i18n_fn.split('/')[-1]
  195. po_fn = str(rosetta_i18n_fn.split('/')[-1])
  196. mo_fn = str(po_fn.replace('.po','.mo')) # not so smart, huh
  197. zipdata = StringIO()
  198. zipf = zipfile.ZipFile(zipdata, mode="w")
  199. zipf.writestr(po_fn, str(rosetta_i18n_pofile))
  200. zipf.writestr(mo_fn, rosetta_i18n_pofile.to_binary())
  201. zipf.close()
  202. zipdata.seek(0)
  203. response = HttpResponse(zipdata.read())
  204. response['Content-Disposition'] = 'attachment; filename=%s.%s.zip' %(offered_fn,rosetta_i18n_lang_code)
  205. response['Content-Type'] = 'application/x-zip'
  206. return response
  207. except Exception, e:
  208. return HttpResponseRedirect(reverse('rosetta-home'))
  209. download_file=user_passes_test(lambda user:can_translate(user),LOGIN_URL)(download_file)
  210. download_file=never_cache(download_file)
  211. def list_languages(request):
  212. """
  213. Lists the languages for the current project, the gettext catalog files
  214. that can be translated and their translation progress
  215. """
  216. languages = []
  217. do_django = 'django' in request.GET
  218. do_rosetta = 'rosetta' in request.GET
  219. has_pos = False
  220. for language in settings.LANGUAGES:
  221. pos = find_pos(language[0],include_djangos=do_django,include_rosetta=do_rosetta)
  222. has_pos = has_pos or len(pos)
  223. languages.append(
  224. (language[0],
  225. _(language[1]),
  226. [(get_app_name(l), os.path.realpath(l), pofile(l)) for l in pos],
  227. )
  228. )
  229. ADMIN_MEDIA_PREFIX = settings.ADMIN_MEDIA_PREFIX
  230. version = rosetta.get_version(True)
  231. return render_to_response('rosetta/languages.html', locals(), context_instance=RequestContext(request))
  232. list_languages=user_passes_test(lambda user:can_translate(user),LOGIN_URL)(list_languages)
  233. list_languages=never_cache(list_languages)
  234. def get_app_name(path):
  235. app = path.split("/locale")[0].split("/")[-1]
  236. return app
  237. def lang_sel(request,langid,idx):
  238. """
  239. Selects a file to be translated
  240. """
  241. if langid not in [l[0] for l in settings.LANGUAGES]:
  242. raise Http404
  243. else:
  244. do_django = 'django' in request.GET
  245. do_rosetta = 'rosetta' in request.GET
  246. file_ = find_pos(langid,include_djangos=do_django,include_rosetta=do_rosetta)[int(idx)]
  247. request.session['rosetta_i18n_lang_code'] = langid
  248. request.session['rosetta_i18n_lang_name'] = unicode([l[1] for l in settings.LANGUAGES if l[0] == langid][0])
  249. request.session['rosetta_i18n_fn'] = file_
  250. po = pofile(file_)
  251. for i in range(len(po)):
  252. po[i].id = i
  253. request.session['rosetta_i18n_pofile'] = po
  254. try:
  255. os.utime(file_,None)
  256. request.session['rosetta_i18n_write'] = True
  257. except OSError:
  258. request.session['rosetta_i18n_write'] = False
  259. return HttpResponseRedirect(reverse('rosetta-home'))
  260. lang_sel=user_passes_test(lambda user:can_translate(user),LOGIN_URL)(lang_sel)
  261. lang_sel=never_cache(lang_sel)
  262. def can_translate(user):
  263. if not user.is_authenticated():
  264. return False
  265. elif user.is_superuser:
  266. return True
  267. else:
  268. try:
  269. from django.contrib.auth.models import Group
  270. translators = Group.objects.get(name='translators')
  271. return translators in user.groups.all()
  272. except Group.DoesNotExist:
  273. return False