1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2008-2009 Zuza Software Foundation
#
# This file is part of Pootle.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import cStringIO
import os
from django.shortcuts import get_object_or_404
from django.http import HttpResponse
from django.utils import simplejson
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext as _N
from django.core.exceptions import PermissionDenied
from pootle_misc.baseurl import redirect
from pootle_app.models import TranslationProject, Directory, store_iteration
from pootle_store.models import Store
from pootle_app.models.permissions import get_matching_permissions, check_permission
from pootle_app.models.profile import get_profile
from pootle_app.views.language import dispatch
from pootle_app.convert import convert_table
from pootle_app import unit_update
from pootle_app.views.language.tp_translate import view as tp_translate_view
from pootle_app.views.language.tp_review import view as tp_review_view
from pootle_app.views.language.admin_permissions import view as tp_admin_permissions_view
from pootle_app.views.language.admin_files import view as tp_admin_files_view
from pootle_app.views.language.project_index import view as project_index_view
from pootle_app.views.language.translate_page import find_and_display
from pootle_app.views.language import search_forms
from pootle_app.views.language.translate_page import get_diff_codes
from pootle_app.views.language.translate_page import highlight_diffs
from pootle_app.views.language.translate_page import get_string_array
def get_translation_project(f):
def decorated_f(request, language_code, project_code, *args, **kwargs):
translation_project = get_object_or_404(TranslationProject, language__code=language_code, project__code=project_code)
return f(request, translation_project, *args, **kwargs)
return decorated_f
def set_request_context(f):
def decorated_f(request, translation_project, *args, **kwargs):
# For now, all permissions in a translation project are
# relative to the root of that translation project.
request.permissions = get_matching_permissions(
get_profile(request.user), translation_project.directory)
request.translation_project = translation_project
return f(request, translation_project, *args, **kwargs)
return decorated_f
################################################################################
@get_translation_project
@set_request_context
def translation_project_admin_permissions(request, translation_project):
return tp_admin_permissions_view(request, translation_project)
@get_translation_project
@set_request_context
def translation_project_admin_files(request, translation_project):
return tp_admin_files_view(request, translation_project)
@get_translation_project
@set_request_context
def translate_page(request, translation_project, dir_path):
def next_store_item(search, store_name, item):
return store_iteration.get_next_match(directory,
store_name,
item,
search)
def prev_store_item(search, store_name, item):
return store_iteration.get_prev_match(directory,
store_name,
item,
search)
directory = translation_project.directory.get_relative(dir_path)
return find_and_display(request, directory, next_store_item, prev_store_item)
@get_translation_project
@set_request_context
def project_index(request, translation_project, dir_path):
directory = get_object_or_404(Directory, pootle_path=translation_project.directory.pootle_path + dir_path)
return project_index_view(request, translation_project, directory)
def handle_translation_file(request, translation_project, file_path):
pootle_path = translation_project.directory.pootle_path + (file_path or '')
store = get_object_or_404(Store, pootle_path=pootle_path)
def get_item(itr, item):
try:
return itr.next()
except StopIteration:
return item
def next_store_item(search, store_name, item):
if 0 <= item < store.getquickstats()['total']:
return store, get_item(search.next_matches(store, item), item - 1)
else:
return store, store.getquickstats()['total'] - 1
def prev_store_item(search, store_name, item):
if store.getquickstats()['total'] > item > 0:
return store, get_item(search.prev_matches(store, item), item + 1)
else:
return store, 0
return find_and_display(request, store.parent, next_store_item, prev_store_item)
@get_translation_project
@set_request_context
def commit_file(request, translation_project, file_path):
if not check_permission("commit", request):
raise PermissionDenied(_("You do not have rights to commit files here"))
pootle_path = translation_project.directory.pootle_path + file_path
store = get_object_or_404(Store, pootle_path=pootle_path)
result = translation_project.commitpofile(request, store)
return redirect(dispatch.show_directory(request, translation_project.directory.pootle_path))
@get_translation_project
@set_request_context
def update_file(request, translation_project, file_path):
if not check_permission("commit", request):
raise PermissionDenied(_("You do not have rights to update files here"))
pootle_path = translation_project.directory.pootle_path + file_path
store = get_object_or_404(Store, pootle_path=pootle_path)
result = translation_project.update_file(request, store)
return redirect(dispatch.show_directory(request, translation_project.directory.pootle_path))
@get_translation_project
@set_request_context
def export_zip(request, translation_project, file_path):
if not check_permission("archive", request):
return redirect(translation_project.pootle_path,
message=_('You do not have the right to create ZIP archives.'))
pootle_path = translation_project.pootle_path + (file_path or '')
try:
path_obj = Directory.objects.get(pootle_path=pootle_path)
except Directory.DoesNotExist:
path_obj = get_object_or_404(Store, pootle_path=pootle_path[:-1])
stores = store_iteration.iter_stores(path_obj)
archivecontents = translation_project.get_archive(stores)
response = HttpResponse(archivecontents, content_type="application/zip")
if file_path.endswith("/"):
file_path = file_path[:-1]
fish, file_path = os.path.split(file_path)
archivename = '%s-%s' % (translation_project.project.code, translation_project.language.code)
archivename += file_path + '.zip'
response['Content-Disposition'] = 'attachment; filename=%s' % archivename
return response
MIME_TYPES = {
"po": "text/x-gettext-translation; charset=%(encoding)s",
"csv": "text/csv; charset=%(encoding)s",
"xlf": "application/x-xliff; charset=%(encoding)s",
"ts": "application/x-linguist; charset=%(encoding)s",
"mo": "application/x-gettext-translation" }
@get_translation_project
@set_request_context
def export(request, translation_project, file_path, format):
store = get_object_or_404(Store, pootle_path=translation_project.directory.pootle_path + file_path)
encoding = getattr(store.file.store, "encoding", "UTF-8")
content_type = MIME_TYPES[format] % dict(encoding=encoding)
if format == translation_project.project.localfiletype:
response = HttpResponse(str(store.file.store), content_type=content_type)
response['Content-Disposition'] = 'attachment; filename=%s' % store.name
else:
convert_func = convert_table[translation_project.project.localfiletype, format]
output_file = cStringIO.StringIO()
input_file = cStringIO.StringIO(str(store.file.store))
convert_func(input_file, output_file, None)
response = HttpResponse(output_file.getvalue(), content_type=content_type)
filename, ext = os.path.splitext(store.name)
filename += os.path.extsep + format
response['Content-Disposition'] = 'attachment; filename=%s' % filename
return response
@get_translation_project
@set_request_context
def handle_file(request, translation_project, file_path):
return handle_translation_file(request, translation_project, file_path)
@get_translation_project
@set_request_context
def handle_suggestions(request, translation_project, file_path, item):
"""Handles accepts/rejects of suggestions selectively via AJAX, receiving
and sending data in JSON format.
Response attributes are described below:
* "status": Indicates the status after trying the action.
Possible values: "error", "success".
* "message": Status message of the transaction. Depending on the status
it will display an error message, or the number of
suggestions rejected/accepted.
* "diffs": Updated diff for the current translation after performing
an action. If there are no suggestions pending, an empty
dict will be returned."""
pootle_path = translation_project.pootle_path + file_path
store = Store.objects.get(pootle_path=pootle_path)
item = int(item)
def get_pending_suggestions(item):
"""Gets pending suggestions for item in pofilename."""
itemsuggestions = []
suggestions = store.getsuggestions(item)
for suggestion in suggestions:
if suggestion.hasplural():
itemsuggestions.append(suggestion.target.strings)
else:
itemsuggestions.append([suggestion.target])
return itemsuggestions
def get_updated_diffs(trans, suggestions):
"""Returns the diff between the current translation and the
suggestions available after performing an accept/reject
action.
If no suggestions are available anymore, just return an
empty list."""
# No suggestions left, no output at all
if len(suggs) == 0:
return []
else:
diffcodes = {}
forms = []
for pluralitem, pluraltrans in enumerate(trans):
pluraldiffcodes = [get_diff_codes(pluraltrans,
suggestion[pluralitem])
for suggestion in suggestions]
diffcodes[pluralitem] = pluraldiffcodes
combineddiffs = reduce(list.__add__, pluraldiffcodes, [])
transdiff = highlight_diffs(pluraltrans, combineddiffs,
issrc=True)
form = { "diff": transdiff }
forms.append(form)
return forms
response = {}
# Decode JSON data sent via POST
data = simplejson.loads(request.POST.get("data", "{}"))
if not data:
response["status"] = "error"
response["message"] = _("No suggestion data given.")
else:
response["del_ids"] = []
rejects = data.get("rejects", [])
reject_candidates = len(rejects)
reject_count = 0
accepts = data.get("accepts", [])
accept_candidates = len(accepts)
accept_count = 0
for sugg in accepts:
try:
unit_update.accept_suggestion(store, item, int(sugg["id"]),
sugg["newtrans"], request)
response["del_ids"].append((item, sugg["id"]))
response["accepted_id"] = (item, sugg["id"])
accept_count += 1
except ValueError, e:
# Probably an issue with "item". The exception might tell us
# everything we need, while no error will probably help the user
response["message"] = e
except PermissionDenied, e:
response["message"] = e
for sugg in reversed(rejects):
try:
unit_update.reject_suggestion(store, int(item), int(sugg["id"]),
sugg["newtrans"], request)
reject_count += 1
response["del_ids"].append((int(item), sugg["id"]))
except ValueError, e:
# Probably an issue with "item". The exception might tell us
# everything we need, while no error will probably help the user
response["message"] = e
except PermissionDenied, e:
response["message"] = e
response["status"] = (reject_candidates == reject_count and
accept_candidates == accept_count) and \
"success" or "error"
if response["status"] == "success":
amsg = ""
rmsg = ""
if accept_candidates != 0:
amsg = _("Suggestion accepted.")
if reject_candidates != 0:
rmsg = _N("Suggestion rejected.",
"%d suggestions rejected.",
reject_count, reject_count)
response["message"] = amsg + rmsg
# Get updated diffs
current_trans = get_string_array(store.file.getitem(item).target)
suggs = get_pending_suggestions(item)
response["diffs"] = get_updated_diffs(current_trans, suggs)
response = simplejson.dumps(response, indent=4)
return HttpResponse(response, mimetype="application/json")
@get_translation_project
@set_request_context
def tp_translate(request, translation_project, dir_path):
directory = get_object_or_404(Directory, pootle_path=translation_project.directory.pootle_path + dir_path)
return tp_translate_view(request, translation_project, directory)
@get_translation_project
@set_request_context
def tp_review(request, translation_project, dir_path):
directory = get_object_or_404(Directory, pootle_path=translation_project.directory.pootle_path + dir_path)
return tp_review_view(request, translation_project, directory)
|