2 # -*- coding: utf-8 -*-
4 # Copyright 2004-2006 Zuza Software Foundation
6 # This file is part of translate.
8 # translate is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 from jToolkit
import spellcheck
24 from Pootle
import pagelayout
25 from Pootle
import projects
26 from Pootle
import pootlefile
27 from translate
.storage
import po
28 from translate
.misc
.multistring
import multistring
32 xml_re
= re
.compile("<.*?>")
34 def oddoreven(polarity
):
37 elif polarity
% 2 == 1:
40 class TranslatePage(pagelayout
.PootleNavPage
):
41 """the page which lets people edit translations"""
42 def __init__(self
, project
, session
, argdict
, dirfilter
=None):
43 self
.argdict
= argdict
44 self
.dirfilter
= dirfilter
45 self
.project
= project
46 self
.altproject
= None
47 # do we have enabled alternative source language?
48 self
.enablealtsrc
= getattr(session
.instance
, "enablealtsrc", "False")
49 if self
.enablealtsrc
== 'True':
50 # try to get the project if the user has chosen an alternate source language
51 altsrc
= session
.getaltsrclanguage()
54 self
.altproject
= self
.project
.potree
.getproject(altsrc
, self
.project
.projectcode
)
57 self
.matchnames
= self
.getmatchnames(self
.project
.checker
)
58 self
.searchtext
= self
.argdict
.get("searchtext", "")
59 # TODO: fix this in jToolkit
60 if isinstance(self
.searchtext
, str):
61 self
.searchtext
= self
.searchtext
.decode("utf8")
62 self
.showassigns
= self
.argdict
.get("showassigns", 0)
63 if isinstance(self
.showassigns
, (str, unicode)) and self
.showassigns
.isdigit():
64 self
.showassigns
= int(self
.showassigns
)
65 self
.session
= session
66 self
.localize
= session
.localize
67 self
.rights
= self
.project
.getrights(self
.session
)
68 self
.instance
= session
.instance
70 self
.pofilename
= self
.argdict
.pop("pofilename", None)
71 if self
.pofilename
== "":
72 self
.pofilename
= None
73 if self
.pofilename
is None and self
.dirfilter
is not None and \
74 (self
.dirfilter
.endswith(".po") or self
.dirfilter
.endswith(".xlf")):
75 self
.pofilename
= self
.dirfilter
76 self
.receivetranslations()
77 # TODO: clean up modes to be one variable
78 self
.viewmode
= self
.argdict
.get("view", 0) and "view" in self
.rights
79 self
.reviewmode
= self
.argdict
.get("review", 0)
80 self
.translatemode
= self
.argdict
.get("translate", 0) or self
.argdict
.get("searchtext", 0) and ("translate" in self
.rights
or "suggest" in self
.rights
)
84 except StopIteration, stoppedby
:
85 notice
= self
.getfinishedtext(stoppedby
)
87 items
= self
.maketable()
88 # self.pofilename can change in search...
89 givenpofilename
= self
.pofilename
90 formaction
= self
.makelink("")
94 rows
= self
.getdisplayrows("view")
97 rows
= self
.getdisplayrows("translate")
99 if self
.pofilename
is not None:
100 postats
= self
.project
.getpostats(self
.pofilename
)
101 untranslated
, fuzzy
= postats
["untranslated"], postats
["fuzzy"]
102 translated
, total
= postats
["translated"], postats
["total"]
103 mainstats
= self
.localize("%d/%d translated\n(%d untranslated, %d fuzzy)", len(translated
), len(total
), len(untranslated
), len(fuzzy
))
104 pagelinks
= self
.getpagelinks("?translate=1&view=1", rows
)
105 navbarpath_dict
= self
.makenavbarpath_dict(self
.project
, self
.session
, self
.pofilename
, dirfilter
=self
.dirfilter
or "")
107 templatename
= "translatepage"
108 instancetitle
= getattr(session
.instance
, "title", session
.localize("Pootle Demo"))
109 # l10n: first parameter: name of the installation (like "Pootle")
110 # l10n: second parameter: project name
111 # l10n: third parameter: target language
112 # l10n: fourth parameter: file name
113 pagetitle
= self
.localize("%s: translating %s into %s: %s", instancetitle
, self
.project
.projectname
, self
.project
.languagename
, self
.pofilename
)
114 language
= {"code": pagelayout
.weblanguage(self
.project
.languagecode
), "name": self
.project
.languagename
, "dir": pagelayout
.languagedir(self
.project
.languagecode
)}
115 sessionvars
= {"status": session
.status
, "isopen": session
.isopen
, "issiteadmin": session
.issiteadmin()}
116 stats
= {"summary": mainstats
, "checks": [], "tracks": [], "assigns": []}
117 templatevars
= {"pagetitle": pagetitle
,
118 "project": {"code": self
.project
.projectcode
, "name": self
.project
.projectname
},
119 "language": language
,
120 "pofilename": self
.pofilename
,
122 "navitems": [{"icon": icon
, "path": navbarpath_dict
, "actions": {}, "stats": stats
}],
123 "pagelinks": pagelinks
,
125 "actionurl": formaction
,
127 # l10n: Heading above the table column with the source language
128 "original_title": self
.localize("Original"),
129 # l10n: Heading above the table column with the target language
130 "translation_title": self
.localize("Translation"),
132 "reviewmode": self
.reviewmode
,
133 "accept_button": self
.localize("Accept"),
134 "reject_button": self
.localize("Reject"),
135 "fuzzytext": self
.localize("Fuzzy"),
136 # l10n: Heading above the textarea for translator comments.
137 "translator_comments_title": self
.localize("Translator comments"),
138 # l10n: Heading above the comments extracted from the programing source code
139 "developer_comments_title": self
.localize("Developer comments"),
140 # l10n: This heading refers to related translations and terminology
141 "related_title": self
.localize("Related"),
142 # optional sections, will appear if these values are replaced
144 # l10n: text next to search field
145 "search": {"title": self
.localize("Search")},
147 "searchtext": self
.searchtext
,
148 "pofilename": givenpofilename
,
150 "session": sessionvars
,
151 "instancetitle": instancetitle
}
153 if self
.showassigns
and "assign" in self
.rights
:
154 templatevars
["assign"] = self
.getassignbox()
155 pagelayout
.PootleNavPage
.__init
__(self
, templatename
, templatevars
, session
, bannerheight
=81)
158 def getfinishedtext(self
, stoppedby
):
159 """gets notice to display when the translation is finished"""
160 # l10n: "batch" refers to the set of translations that were reviewed
161 title
= self
.localize("End of batch")
162 finishedlink
= "index.html?" + "&".join(["%s=%s" % (arg
, value
) for arg
, value
in self
.argdict
.iteritems() if arg
.startswith("show") or arg
== "editing"])
163 returnlink
= self
.localize("Click here to return to the index")
164 stoppedbytext
= stoppedby
.args
[0]
165 return {"title": title
, "stoppedby": stoppedbytext
, "finishedlink": finishedlink
, "returnlink": returnlink
}
167 def getpagelinks(self
, baselink
, pagesize
):
168 """gets links to other pages of items, based on the given baselink"""
169 baselink
+= "&pofilename=%s" % self
.pofilename
171 pofilelen
= self
.project
.getpofilelen(self
.pofilename
)
172 if pofilelen
<= pagesize
or self
.firstitem
is None:
174 lastitem
= min(pofilelen
-1, self
.firstitem
+ pagesize
- 1)
175 if pofilelen
> pagesize
and not self
.firstitem
== 0:
176 # l10n: noun (the start)
177 pagelinks
.append({"href": baselink
+ "&item=0", "text": self
.localize("Start")})
179 # l10n: noun (the start)
180 pagelinks
.append({"text": self
.localize("Start")})
181 if self
.firstitem
> 0:
182 linkitem
= max(self
.firstitem
- pagesize
, 0)
183 # l10n: the parameter refers to the number of messages
184 pagelinks
.append({"href": baselink
+ "&item=%d" % linkitem
, "text": self
.localize("Previous %d", (self
.firstitem
- linkitem
))})
186 # l10n: the parameter refers to the number of messages
187 pagelinks
.append({"text": self
.localize("Previous %d", pagesize
)})
188 # l10n: the third parameter refers to the total number of messages in the file
189 pagelinks
.append({"text": self
.localize("Items %d to %d of %d", self
.firstitem
+1, lastitem
+1, pofilelen
)})
190 if self
.firstitem
+ len(self
.translations
) < self
.project
.getpofilelen(self
.pofilename
):
191 linkitem
= self
.firstitem
+ pagesize
192 itemcount
= min(pofilelen
- linkitem
, pagesize
)
193 # l10n: the parameter refers to the number of messages
194 pagelinks
.append({"href": baselink
+ "&item=%d" % linkitem
, "text": self
.localize("Next %d", itemcount
)})
196 # l10n: the parameter refers to the number of messages
197 pagelinks
.append({"text": self
.localize("Next %d", pagesize
)})
198 if pofilelen
> pagesize
and (self
.item
+ pagesize
) < pofilelen
:
199 # l10n: noun (the end)
200 pagelinks
.append({"href": baselink
+ "&item=%d" % max(pofilelen
- pagesize
, 0), "text": self
.localize("End")})
202 # l10n: noun (the end)
203 pagelinks
.append({"text": self
.localize("End")})
204 for n
, pagelink
in enumerate(pagelinks
):
205 if n
< len(pagelinks
)-1:
206 pagelink
["sep"] = " | "
211 def addfilelinks(self
):
212 """adds a section on the current file, including any checks happening"""
213 if self
.showassigns
and "assign" in self
.rights
:
214 self
.templatevars
["assigns"] = self
.getassignbox()
215 if self
.pofilename
is not None:
217 checknames
= [matchname
.replace("check-", "", 1) for matchname
in self
.matchnames
]
218 # TODO: put the following parameter in quotes, since it will be foreign in all target languages
219 # l10n: the parameter is the name of one of the quality checks, like "fuzzy"
220 self
.templatevars
["checking_text"] = self
.localize("checking %s", ", ".join(checknames
))
222 def getassignbox(self
):
223 """gets strings if the user can assign strings"""
224 users
= [username
for username
, userprefs
in self
.session
.loginchecker
.users
.iteritems() if username
!= "__dummy__"]
227 "title": self
.localize("Assign Strings"),
228 "user_title": self
.localize("Assign to User"),
229 "action_title": self
.localize("Assign Action"),
230 "submit_text": self
.localize("Assign Strings"),
234 def receivetranslations(self
):
235 """receive any translations submitted by the user"""
236 if self
.pofilename
is None:
248 keymatcher
= re
.compile("(\D+)([0-9.]+)")
250 match
= keymatcher
.match(key
)
252 keytype
, itemcode
= match
.groups()
253 return keytype
, itemcode
255 def pointsplit(item
):
256 dotcount
= item
.count(".")
258 item
, pointitem
, subpointitem
= item
.split(".", 2)
259 return int(item
), int(pointitem
), int(subpointitem
)
261 item
, pointitem
= item
.split(".", 1)
262 return int(item
), int(pointitem
), None
264 return int(item
), None, None
266 for key
, value
in self
.argdict
.iteritems():
267 keytype
, item
= parsekey(key
)
270 item
, pointitem
, subpointitem
= pointsplit(item
)
271 if keytype
== "skip":
273 elif keytype
== "back":
275 elif keytype
== "submitsuggest":
276 submitsuggests
.append(item
)
277 elif keytype
== "submit":
279 elif keytype
== "accept":
280 accepts
.append((item
, pointitem
))
281 elif keytype
== "reject":
282 rejects
.append((item
, pointitem
))
283 elif keytype
== "translator_comments":
284 # We need to remove carriage returns from the input.
285 value
= value
.replace("\r", "")
286 comments
[item
] = value
287 elif keytype
== "fuzzy":
288 fuzzies
[item
] = value
289 elif keytype
== "trans":
290 value
= self
.unescapesubmition(value
)
291 if pointitem
is not None:
292 translations
.setdefault(item
, {})[pointitem
] = value
294 translations
[item
] = value
295 elif keytype
== "suggest":
296 suggestions
.setdefault((item
, pointitem
), {})[subpointitem
] = value
297 elif keytype
== "orig-pure":
298 # this is just to remove the hidden fields from the argdict
306 del self
.argdict
[key
]
310 self
.lastitem
= item
- 2
311 for item
in submitsuggests
:
312 if item
in skips
or item
not in translations
:
314 value
= translations
[item
]
315 self
.project
.suggesttranslation(self
.pofilename
, item
, value
, self
.session
)
319 if item
in skips
or item
not in translations
:
323 newvalues
["target"] = translations
[item
]
324 if isinstance(newvalues
["target"], dict) and len(newvalues
["target"]) == 1 and 0 in newvalues
["target"]:
325 newvalues
["target"] = newvalues
["target"][0]
327 newvalues
["fuzzy"] = False
328 if (fuzzies
.get(item
) == u
'on'):
329 newvalues
["fuzzy"] = True
331 translator_comments
= comments
.get(item
)
332 if translator_comments
:
333 newvalues
["translator_comments"] = translator_comments
335 self
.project
.updatetranslation(self
.pofilename
, item
, newvalues
, self
.session
)
338 for item
, suggid
in rejects
:
339 value
= suggestions
[item
, suggid
]
340 if isinstance(value
, dict) and len(value
) == 1 and 0 in value
:
342 self
.project
.rejectsuggestion(self
.pofilename
, item
, suggid
, value
, self
.session
)
344 for item
, suggid
in accepts
:
345 if (item
, suggid
) in rejects
or (item
, suggid
) not in suggestions
:
347 value
= suggestions
[item
, suggid
]
348 if isinstance(value
, dict) and len(value
) == 1 and 0 in value
:
350 self
.project
.acceptsuggestion(self
.pofilename
, item
, suggid
, value
, self
.session
)
353 def getmatchnames(self
, checker
):
354 """returns any checker filters the user has asked to match..."""
356 for checkname
in self
.argdict
:
357 if checkname
in ["fuzzy", "untranslated", "translated"]:
358 matchnames
.append(checkname
)
359 elif checkname
in checker
.getfilters():
360 matchnames
.append("check-" + checkname
)
364 def getusernode(self
):
365 """gets the user's prefs node"""
366 if self
.session
.isopen
:
367 return getattr(self
.session
.loginchecker
.users
, self
.session
.username
.encode("utf-8"), None)
372 """finds the focussed item for this page, searching as neccessary"""
373 item
= self
.argdict
.pop("item", None)
376 search
= pootlefile
.Search(dirfilter
=self
.dirfilter
, matchnames
=self
.matchnames
, searchtext
=self
.searchtext
)
377 # TODO: find a nicer way to let people search stuff assigned to them (does it by default now)
378 # search.assignedto = self.argdict.get("assignedto", self.session.username)
379 search
.assignedto
= self
.argdict
.get("assignedto", None)
380 search
.assignedaction
= self
.argdict
.get("assignedaction", None)
381 self
.pofilename
, self
.item
= self
.project
.searchpoitems(self
.pofilename
, self
.lastitem
, search
).next()
382 except StopIteration:
383 if self
.lastitem
is None:
384 raise StopIteration(self
.localize("There are no items matching that search ('%s')", self
.searchtext
))
386 raise StopIteration(self
.localize("You have finished going through the items you selected"))
388 if not item
.isdigit():
389 raise ValueError("Invalid item given")
390 self
.item
= int(item
)
391 if self
.pofilename
is None:
392 raise ValueError("Received item argument but no pofilename argument")
393 self
.project
.track(self
.pofilename
, self
.item
, "being edited by %s" % self
.session
.username
)
395 def getdisplayrows(self
, mode
):
396 """get the number of rows to display for the given mode"""
398 prefsfield
= "viewrows"
401 elif mode
== "translate":
402 prefsfield
= "translaterows"
406 raise ValueError("getdisplayrows has no mode '%s'" % mode
)
407 usernode
= self
.getusernode()
408 rowsdesired
= getattr(usernode
, prefsfield
, default
)
409 if isinstance(rowsdesired
, basestring
):
410 if rowsdesired
== "":
411 rowsdesired
= default
413 rowsdesired
= int(rowsdesired
)
414 rowsdesired
= min(rowsdesired
, maximum
)
417 def gettranslations(self
):
418 """gets the list of translations desired for the view, and sets editable and firstitem parameters"""
419 if self
.item
is None:
421 self
.firstitem
= self
.item
425 self
.firstitem
= self
.item
426 rows
= self
.getdisplayrows("view")
427 return self
.project
.getitems(self
.pofilename
, self
.item
, self
.item
+rows
)
429 self
.editable
= [self
.item
]
430 rows
= self
.getdisplayrows("translate")
432 fromitem
= self
.item
- before
433 self
.firstitem
= max(self
.item
- before
, 0)
434 toitem
= self
.firstitem
+ rows
435 return self
.project
.getitems(self
.pofilename
, fromitem
, toitem
)
438 self
.translations
= self
.gettranslations()
440 if self
.reviewmode
and self
.item
is not None:
441 suggestions
= {self
.item
: self
.project
.getsuggestions(self
.pofilename
, self
.item
)}
442 for row
, unit
in enumerate(self
.translations
):
444 if isinstance(unit
.source
, multistring
):
445 orig
= unit
.source
.strings
448 if isinstance(unit
.target
, multistring
):
449 trans
= unit
.target
.strings
451 trans
= [unit
.target
]
452 nplurals
, plurals
= self
.project
.getpofile(self
.pofilename
).getheaderplural()
455 if not (nplurals
and nplurals
.isdigit()):
456 # The file doesn't have plural information declared. Let's get it from
458 nplurals
= getattr(getattr(self
.session
.instance
.languages
, self
.project
.languagecode
, None), "nplurals", "")
459 nplurals
= int(nplurals
)
460 if len(trans
) != nplurals
:
461 # Chop if in case it is too long
462 trans
= trans
[:nplurals
]
463 trans
.extend([u
""]* (nplurals
-len(trans
)))
465 # Something went wrong, lets just give nothing
467 item
= self
.firstitem
+ row
468 origdict
= self
.getorigdict(item
, orig
, item
in self
.editable
)
472 if item
in self
.editable
:
473 translator_comments
= unit
.getnotes(origin
="translator")
474 developer_comments
= self
.escapetext(unit
.getnotes(origin
="developer"), stripescapes
=True)
475 locations
= " ".join(unit
.getlocations())
476 if isinstance(unit
, po
.pounit
):
477 message_context
= "".join(unit
.getcontext())
478 tmsuggestions
= self
.project
.gettmsuggestions(self
.pofilename
, self
.item
)
479 tmsuggestions
.extend(self
.project
.getterminology(self
.session
, self
.pofilename
, self
.item
))
482 translator_comments
= self
.escapetext(unit
.getnotes(origin
="translator"), stripescapes
=True)
483 itemsuggestions
= [suggestion
.target
.strings
for suggestion
in suggestions
[item
]]
484 transmerge
= self
.gettransreview(item
, trans
, itemsuggestions
)
486 transmerge
= self
.gettransedit(item
, trans
)
488 translator_comments
= unit
.getnotes(origin
="translator")
489 developer_comments
= unit
.getnotes(origin
="developer")
491 transmerge
= self
.gettransview(item
, trans
)
492 transdict
= {"itemid": "trans%d" % item
,
493 "focus_class": origdict
["focus_class"],
494 "isplural": len(trans
) > 1,
496 transdict
.update(transmerge
)
497 polarity
= oddoreven(item
)
498 if item
in self
.editable
:
499 focus_class
= "translate-focus"
506 state_class
+= "translate-translation-fuzzy"
512 "polarity": polarity
,
513 "focus_class": focus_class
,
514 "editable": item
in self
.editable
,
515 "state_class": state_class
,
517 "translator_comments": translator_comments
,
518 "developer_comments": developer_comments
,
519 "locations": locations
,
520 "message_context": message_context
,
524 altsrcdict
= {"available": False}
525 # do we have enabled alternative source language?
526 if self
.enablealtsrc
== 'True':
527 # get alternate source project information in a dictionary
528 if item
in self
.editable
:
529 altsrcdict
= self
.getaltsrcdict(origdict
)
530 itemdict
["altsrc"] = altsrcdict
532 items
.append(itemdict
)
535 def fancyspaces(self
, string
):
536 """Returns the fancy spaces that are easily visible."""
537 spaces
= string
.group()
538 while spaces
[0] in "\t\n\r":
540 return '<span class="translation-space"> </span>\n' * len(spaces
)
542 def escapetext(self
, text
, fancyspaces
=True, stripescapes
=False):
543 """Replace special characters &, <, >, add and handle escapes if asked."""
544 text
= text
.replace("&", "&") # Must be done first!
545 text
= text
.replace("<", "<").replace(">", ">")
548 text
= text
.replace("\n", '<br />')
549 text
= text
.replace("\r", '<br />')
551 fancyescape
= lambda escape
: \
552 '<span class="translation-highlight-escape">%s</span>' % escape
553 fancy_xml
= lambda escape
: \
554 '<span class="translation-highlight-html">%s</span>' % escape
.group()
555 text
= xml_re
.sub(fancy_xml
, text
)
557 text
= text
.replace("\r\n", fancyescape('\\r\\n') + '<br />')
558 text
= text
.replace("\n", fancyescape('\\n') + '<br />')
559 text
= text
.replace("\r", fancyescape('\\r') + '<br />')
560 text
= text
.replace("\t", fancyescape('\\t'))
561 text
= text
.replace("<br />", '<br />\n')
562 # we don't need it at the end of the string
563 if text
.endswith("<br />\n"):
564 text
= text
[:-len("<br />\n")]
567 text
= self
.addfancyspaces(text
)
570 def addfancyspaces(self
, text
):
571 """Insert fancy spaces"""
572 #More than two consecutive:
573 text
= re
.sub("[ ]{2,}", self
.fancyspaces
, text
)
575 text
= re
.sub("^[ ]+", self
.fancyspaces
, text
)
577 text
= re
.sub("\\n([ ]+)", self
.fancyspaces
, text
)
579 text
= re
.sub("[ ]+$", self
.fancyspaces
, text
)
582 def escapefortextarea(self
, text
):
583 text
= text
.replace("&", "&") # Must be done first!
584 text
= text
.replace("<", "<").replace(">", ">")
585 text
= text
.replace("\r\n", '\\r\\n')
586 text
= text
.replace("\n", '\\n')
587 text
= text
.replace("\\n", '\\n\n')
588 text
= text
.replace("\t", '\\t')
591 def unescapesubmition(self
, text
):
592 text
= text
.replace("\t", "")
593 text
= text
.replace("\n", "")
594 text
= text
.replace("\r", "")
595 text
= text
.replace("\\t", "\t")
596 text
= text
.replace("\\n", "\n")
597 text
= text
.replace("\\r", "\r")
600 def getorigdict(self
, item
, orig
, editable
):
602 focus_class
= "translate-original-focus"
604 focus_class
= "autoexpand"
606 for pluralid
, pluraltext
in enumerate(orig
):
607 pureid
= "orig-pure%d.%d" % (item
, pluralid
)
608 purefields
.append({"pureid": pureid
, "name": pureid
, "value": pluraltext
})
610 "focus_class": focus_class
,
611 "itemid": "orig%d" % item
,
613 "isplural": len(orig
) > 1 or None,
614 "singular_title": self
.localize("Singular"),
615 "plural_title": self
.localize("Plural"),
618 origdict
["singular_text"] = self
.escapetext(orig
[0])
619 origdict
["plural_text"] = self
.escapetext(orig
[1])
621 origdict
["text"] = self
.escapetext(orig
[0])
624 def geteditlink(self
, item
):
625 """gets a link to edit the given item, if the user has permission"""
626 if "translate" in self
.rights
or "suggest" in self
.rights
:
627 translateurl
= "?translate=1&item=%d&pofilename=%s" % (item
, urllib
.quote(self
.pofilename
, '/'))
629 return {"href": translateurl
, "text": self
.localize("Edit"), "linkid": "editlink%d" % item
}
633 def gettransbuttons(self
, item
, desiredbuttons
):
634 """gets buttons for actions on translation"""
635 if "suggest" in desiredbuttons
and "suggest" not in self
.rights
:
636 desiredbuttons
.remove("suggest")
637 if "translate" in desiredbuttons
and "translate" not in self
.rights
:
638 desiredbuttons
.remove("translate")
639 specialchars
= getattr(getattr(self
.session
.instance
.languages
, self
.project
.languagecode
, None), "specialchars", "")
640 if isinstance(specialchars
, str):
641 specialchars
= specialchars
.decode("utf-8")
642 return {"desired": desiredbuttons
,
645 "copy_text": self
.localize("Copy"),
646 "skip": self
.localize("Skip"),
648 "back": self
.localize("Back"),
649 "suggest": self
.localize("Suggest"),
650 "submit": self
.localize("Submit"),
651 "specialchars": specialchars
,
652 # l10n: action that increases the height of the textarea
653 "grow": self
.localize("Grow"),
654 # l10n: action that decreases the height of the textarea
655 "shrink": self
.localize("Shrink"),
656 # l10n: action that increases the width of the textarea
659 def gettransedit(self
, item
, trans
):
660 """returns a widget for editing the given item and translation"""
665 if "translate" in self
.rights
or "suggest" in self
.rights
:
666 usernode
= self
.getusernode()
668 "rows": getattr(usernode
, "inputheight", 5),
669 "cols": getattr(usernode
, "inputwidth", 40),
672 spellargs
= {"standby_url": "spellingstandby.html", "js_url": "/js/spellui.js", "target_url": "spellcheck.html"}
674 buttons
= self
.gettransbuttons(item
, ["back", "skip", "copy", "suggest", "translate"])
676 for pluralitem
, pluraltext
in enumerate(trans
):
677 pluralform
= self
.localize("Plural Form %d", pluralitem
)
678 pluraltext
= self
.escapefortextarea(pluraltext
)
679 textid
= "trans%d.%d" % (item
, pluralitem
)
680 forms
.append({"title": pluralform
, "name": textid
, "text": pluraltext
, "n": pluralitem
})
683 transdict
["forms"] = forms
685 buttons
= self
.gettransbuttons(item
, ["back", "skip", "copy", "suggest", "translate", "resize"])
686 transdict
["text"] = self
.escapefortextarea(trans
[0])
687 textid
= "trans%d" % item
690 # Perhaps there is no plural information available
691 buttons
= self
.gettransbuttons(item
, ["back", "skip"])
692 # l10n: This is an error message that will display if the relevant problem occurs
693 transdict
["text"] = self
.escapefortextarea(self
.localize("Translation not possible because plural information for your language is not available. Please contact the site administrator."))
694 textid
= "trans%d" % item
697 transdict
["can_spell"] = spellcheck
.can_check_lang(self
.project
.languagecode
)
698 transdict
["spell_args"] = spellargs
699 transdict
["buttons"] = buttons
700 transdict
["focusbox"] = focusbox
702 # TODO: work out how to handle this (move it up?)
703 transdict
.update(self
.gettransview(item
, trans
, textarea
=True))
704 buttons
= self
.gettransbuttons(item
, ["back", "skip"])
705 transdict
["buttons"] = buttons
708 def highlightdiffs(self
, text
, diffs
, issrc
=True):
709 """highlights the differences in diffs in the text.
710 diffs should be list of diff opcodes
711 issrc specifies whether to use the src or destination positions in reconstructing the text
712 this escapes the text on the fly to prevent confusion in escaping the highlighting"""
714 diffstart
= [(i1
, 'start', tag
) for (tag
, i1
, i2
, j1
, j2
) in diffs
if tag
!= 'equal']
715 diffstop
= [(i2
, 'stop', tag
) for (tag
, i1
, i2
, j1
, j2
) in diffs
if tag
!= 'equal']
717 diffstart
= [(j1
, 'start', tag
) for (tag
, i1
, i2
, j1
, j2
) in diffs
if tag
!= 'equal']
718 diffstop
= [(j2
, 'stop', tag
) for (tag
, i1
, i2
, j1
, j2
) in diffs
if tag
!= 'equal']
719 diffswitches
= diffstart
+ diffstop
725 for i
, switch
, tag
in diffswitches
:
726 textsection
= self
.escapetext(text
[textpos
:i
])
727 textdiff
+= textsection
730 if switch
== 'start':
732 elif switch
== 'stop':
734 if switch
== 'start' and textnest
== 1:
735 # start of a textition
736 textdiff
+= "<span class='translate-diff-%s'>" % tag
738 elif switch
== 'stop' and textnest
== 0:
739 # start of an equals block
741 # FIXME: work out why kid swallows empty spans, and browsers display them horribly, then remove this
743 textdiff
+= "</span>"
745 textdiff
+= self
.escapetext(text
[textpos
:])
748 def getdiffcodes(self
, cmp1
, cmp2
):
749 """compares the two strings and returns opcodes"""
750 return difflib
.SequenceMatcher(None, cmp1
, cmp2
).get_opcodes()
752 def gettransreview(self
, item
, trans
, suggestions
):
753 """returns a widget for reviewing the given item's suggestions"""
754 hasplurals
= len(trans
) > 1
756 for pluralitem
, pluraltrans
in enumerate(trans
):
757 if isinstance(pluraltrans
, str):
758 trans
[pluralitem
] = pluraltrans
.decode("utf-8")
759 for suggestion
in suggestions
:
760 for pluralitem
, pluralsugg
in enumerate(suggestion
):
761 if isinstance(pluralsugg
, str):
762 suggestion
[pluralitem
] = pluralsugg
.decode("utf-8")
764 for pluralitem
, pluraltrans
in enumerate(trans
):
765 pluraldiffcodes
= [self
.getdiffcodes(pluraltrans
, suggestion
[pluralitem
]) for suggestion
in suggestions
]
766 diffcodes
[pluralitem
] = pluraldiffcodes
767 combineddiffs
= reduce(list.__add
__, pluraldiffcodes
, [])
768 transdiff
= self
.highlightdiffs(pluraltrans
, combineddiffs
, issrc
=True)
769 form
= {"n": pluralitem
, "diff": transdiff
, "title": None}
771 pluralform
= self
.localize("Plural Form %d", pluralitem
)
772 form
["title"] = pluralform
775 "current_title": self
.localize("Current Translation:"),
776 "editlink": self
.geteditlink(item
),
778 "isplural": hasplurals
or None,
779 "itemid": "trans%d" % item
,
782 for suggid
, msgstr
in enumerate(suggestions
):
783 suggestedby
= self
.project
.getsuggester(self
.pofilename
, item
, suggid
)
784 if len(suggestions
) > 1:
786 # l10n: First parameter: number
787 # l10n: Second parameter: name of translator
788 suggtitle
= self
.localize("Suggestion %d by %s:", suggid
+1, suggestedby
)
790 suggtitle
= self
.localize("Suggestion %d:", suggid
+1)
793 # l10n: parameter: name of translator
794 suggtitle
= self
.localize("Suggestion by %s:", suggestedby
)
796 suggtitle
= self
.localize("Suggestion:")
798 for pluralitem
, pluraltrans
in enumerate(trans
):
799 pluralsuggestion
= msgstr
[pluralitem
]
800 suggdiffcodes
= diffcodes
[pluralitem
][suggid
]
801 suggdiff
= self
.highlightdiffs(pluralsuggestion
, suggdiffcodes
, issrc
=False)
802 if isinstance(pluralsuggestion
, str):
803 pluralsuggestion
= pluralsuggestion
.decode("utf8")
804 form
= {"diff": suggdiff
}
805 form
["suggid"] = "suggest%d.%d.%d" % (item
, suggid
, pluralitem
)
806 form
["value"] = pluralsuggestion
808 form
["title"] = self
.localize("Plural Form %d", pluralitem
)
810 suggdict
= {"title": suggtitle
,
812 "suggid": "%d.%d" % (item
, suggid
),
813 "canreview": "review" in self
.rights
,
817 suggitems
.append(suggdict
)
819 backbutton
= {"item": item
, "text": self
.localize("Back")}
820 skipbutton
= {"item": item
, "text": self
.localize("Skip")}
822 suggitems
[-1]["back"] = backbutton
823 suggitems
[-1]["skip"] = skipbutton
825 transdict
["back"] = backbutton
826 transdict
["skip"] = skipbutton
827 transdict
["suggestions"] = suggitems
830 def gettransview(self
, item
, trans
, textarea
=False):
831 """returns a widget for viewing the given item's translation"""
833 escapefunction
= self
.escapefortextarea
835 escapefunction
= self
.escapetext
836 editlink
= self
.geteditlink(item
)
837 transdict
= {"editlink": editlink
}
840 for pluralitem
, pluraltext
in enumerate(trans
):
841 form
= {"title": self
.localize("Plural Form %d", pluralitem
), "n": pluralitem
, "text": escapefunction(pluraltext
)}
843 transdict
["forms"] = forms
845 transdict
["text"] = escapefunction(trans
[0])
847 # Error, problem with plurals perhaps?
848 transdict
["text"] = ""
851 def getaltsrcdict(self
, origdict
):
852 # TODO: handle plurals !!
853 altsrcdict
= {"available": False}
854 if self
.altproject
is not None:
855 altsrcdict
["languagecode"] = pagelayout
.weblanguage(self
.altproject
.languagecode
)
856 altsrcdict
["languagename"] = self
.altproject
.potree
.getlanguagename(self
.altproject
.languagecode
)
857 altsrcdict
["dir"] = pagelayout
.languagedir(altsrcdict
["languagecode"])
858 altsrcdict
["title"] = self
.session
.tr_lang(altsrcdict
["languagename"])
859 if not origdict
["isplural"]:
860 altsrctext
= self
.altproject
.ugettext(origdict
["text"])
861 if not origdict
["isplural"] and altsrctext
!= origdict
["text"] and not self
.reviewmode
:
862 altsrcdict
["text"] = altsrctext
863 altsrcdict
["available"] = True