def project(userauth, prjname, lngname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata lng = pdata.languages.get(lngname) if lng is None: abort(404, "Language does not exist in the project") return assert pdata.projtype.allow_case or lng.case == [""] blng = pdata.get_base_language( ) # As above we established there is at least one language, this should work. stored = [[] for i in range(data.MAX_STATE)] sdict = pdata.statistics sdict = sdict.get(lngname) # Statistics dict for the queried language. if sdict is None: abort(404, "Missing language statistics") return for sname, bchgs in blng.changes.items(): cstates = sdict[sname] state = max(s[1] for s in cstates) if state != data.MISSING_OK: bchg = data.get_newest_change(bchgs, "") sdd = StringDisplayData(sname, bchg.base_text) chgs = lng.changes.get(sname) if chgs is not None: cases = data.get_all_newest_changes(chgs, lng.case) for case, cstate in cstates: chg = cases[case] if chg is not None: if lng is blng: text = chg.base_text.text else: text = chg.new_text.text if text == "" and case != "": # Suppress empty non-default case from translations. continue cdd = CaseDisplayData(case, data.STATE_MAP[cstate].name, text) sdd.cases.append(cdd) stored[state].append(sdd) for strs in stored: strs.sort() return template("translation", userauth=userauth, pmd=pmd, is_blng=(lng == blng), lng=lng, stored=stored)
def handle_upload(userauth, pmd, projname, langfile, override, is_base, lng_data): """ Process the upload. @param userauth: User authentication. @type userauth: L{UserAuthentication} @param pmd: Project meta data. @type pmd: L{ProjectMetaData} @param projname: Project name. @type projname: C{str} @param langfile: Language file to load if available. @type langfile: C{file} or C{None} @param override: Override existing text. @type override: C{bool} @param lng_data: Used language, if provided. @type lng_data: L{LanguageData} or C{None} """ pdata = pmd.pdata # Missing language file in the upload. if not langfile or not langfile.file: abort(404, "Missing language file") return base_language = pdata.get_base_language() # Cannot download a translation without base language. if not is_base and base_language is None: abort(404, "Project has no base language") return # Parse language file, and report any errors. ng_data = language_file.load_language_file(pdata.projtype, langfile.file, config.cfg.language_file_size, lng_data) if len(ng_data.errors) > 0: return template('upload_errors', userauth=userauth, pmd=pmd, errors=ng_data.errors) # Is the language allowed? if not pdata.projtype.allow_language(ng_data.language_data): abort( 404, "Language \"{}\" may not be uploaded".format( ng_data.language_data.isocode)) return stamp = data.make_stamp() lng = pdata.languages.get(ng_data.language_data.isocode) if lng is None: # New language being added. result = add_new_language(ng_data, pdata, is_base) if not result[0]: abort(404, result[0]) return lng = result[1] if is_base and base_language is None: base_language = lng if is_base: if base_language is not None and base_language != lng: abort(404, "Cannot change a translation to a base language") return # Add strings as changes. for sv in ng_data.strings: sv.text = language_file.sanitize_text(sv.text) chgs = base_language.changes.get(sv.name) chg = get_blng_change(sv, base_language) if chg is None: # New change. base_text = data.Text(sv.text, sv.case, stamp) chg = data.Change(sv.name, sv.case, base_text, None, stamp, userauth.name, True) if chgs is None: chgs = [chg] base_language.changes[sv.name] = chgs else: chgs.append(chg) else: # Only way to update a base language is by upload, no need for override check. chg.stamp = stamp chg.user = userauth.name for c in chgs: c.last_upload = (c == chg) # Update language properties as well. copy_lng_properties(pdata.projtype, ng_data, base_language) pdata.skeleton = ng_data.skeleton # Use the new skeleton file. pdata.flush_related_cache() # Drop the related strings cache. pdata.set_modified() # Push the new set of string-names to all languages (this includes the base language). str_names = set(sv.name for sv in ng_data.strings) for lang in pdata.languages.values(): lng_modified = False not_seen = str_names.copy() for sn in list(lang.changes.keys()): not_seen.discard(sn) if sn in str_names: continue # Name is kept. del lang.changes[sn] # Old string, delete lng_modified = True for sn in not_seen: # Missing translation are not saved, so no set_modified here. lang.changes[sn] = [] if lng_modified: lang.set_modified() else: # Not a base language -> it is a translation. if base_language is not None and base_language == lng: abort(404, "Cannot change a base language to a translation") return for sv in ng_data.strings: sv.text = language_file.sanitize_text(sv.text) # Find base language string for 'sv'. bchgs = base_language.changes.get(sv.name) if bchgs is None: continue # Translation has a string not in the base language bchg = data.get_newest_change(bchgs, '') if bchg is None: continue # Nothing to base against. base_text = bchg.base_text chgs = lng.changes.get(sv.name) chg = get_lng_change(sv, lng, base_text) if chg is None: # It's a new text or new case. lng_text = data.Text(sv.text, sv.case, stamp) chg = data.Change(sv.name, sv.case, base_text, lng_text, stamp, userauth.name, True) if chgs is None: lng.changes[sv.name] = [chg] else: for c in chgs: c.last_upload = False chgs.append(chg) elif override: # Override existing entry. chg.stamp = stamp chg.user = userauth.name for c in chgs: c.last_upload = (c == chg) # Update language properties as well. copy_lng_properties(pdata.projtype, ng_data, lng) lng.set_modified() config.cache.save_pmd(pmd) if is_base: pmd.create_statistics(None) # Update all languages. else: pmd.create_statistics(lng) message = "Successfully uploaded language '" + lng.name + "' " + utils.get_datetime_now_formatted( ) redirect("/project/<prjname>", prjname=projname, message=message)
def output_string_edit_page(userauth, bchg, binfo, lng, pmd, lngname, sname, states=None, messages=[]): """ Construct a page for editing a string. @param bchg: Last version of the base language. @type bchg: L{Change} @param binfo: Information about the state of the L{bchg} string. @type binfo: L{StringInfo} @param lng: Language being translated. @type lng: L{Language} @param pmd: Project Meta Data. @type pmd: L{ProjectMetaData} @param lngname: Language name. @type lngname: C{str} @param sname: Name of the string. @type sname: C{str} @param states: Changes, state and errors for (some of) the cases, omission of a case means the function should derive it from the language. @type states: C{dict} of C{str} to tuple (L{Change}, C{int}, C{list} of L{ErrorMessage}), use C{None} to derive all. @return: Either an error, or an instantiated template. @rtype: C{None} or C{str} """ if states is None: states = {} pdata = pmd.pdata projtype = pdata.projtype # Mapping of case to list of related strings. related_cases = dict((case, []) for case in lng.case) for rel_sname in pdata.get_related_strings(sname): rel_chgs = lng.changes.get(rel_sname) if rel_chgs is not None: rel_chgs = data.get_all_newest_changes(rel_chgs, lng.case) for case, chg in rel_chgs.items(): if chg is not None and chg.new_text is not None: rc = related_cases.get(case) if rc is not None: rc.append(RelatedString(rel_sname, chg.new_text)) case_chgs = data.get_all_changes(lng.changes.get(sname), lng.case, None) now = data.make_stamp() transl_cases = [] for case in lng.case: tranls = [] cchgs = [(cchg, True) for cchg in case_chgs[case] ] # Tuples (case-change, 'saved translation') chg_err_state = states.get(case) if chg_err_state is not None: cchgs.append((chg_err_state[0], False)) if len(cchgs) == 0: # No changes for this case, make a dummy one to display the base data. tra = Translation(bchg, None, now, False) if case == "": tra.errors = [ language_file.ErrorMessage(language_file.ERROR, None, "String is missing") ] tra.state = data.STATE_MAP[data.MISSING].name else: tra.errors = [] tra.state = data.STATE_MAP[data.MISSING_OK].name tranls.append(tra) else: # Changes do exist, add them (in reverse chronological order). cchgs.reverse() for idx, (lchg, saved) in enumerate(cchgs): tra = Translation(bchg, lchg, now, saved) if idx == 0: # Newest string, add errors if chg_err_state is not None: state, errors = chg_err_state[1], chg_err_state[2] else: state, errors = data.get_string_status( projtype, lchg, case, lng, bchg.base_text, binfo) tra.errors = errors tra.state = data.STATE_MAP[state].name else: # For older translations, the errors and state are never displayed. tra.errors = [] tra.state = data.STATE_MAP[data.MISSING_OK].name tranls.append(tra) if projtype.allow_case or case == "": transl_cases.append( TransLationCase(case, tranls, related_cases[case])) related_languages = [] if lng.name[:3] != pdata.base_language[:3]: for n, l in pdata.languages.items(): if n[:3] != lng.name[:3] or n == lng.name: continue related = data.get_newest_change(lng.changes.get(sname), "") if related is not None: related_languages.append((l, related)) related_languages.sort(key=lambda x: x[0].name) return template( "string_form", userauth=userauth, pmd=pmd, lng=lng, sname=sname, plurals=language_info.all_plurals[lng.plural].description, genders=lng.gender, cases=lng.case, related_languages=related_languages, tcs=transl_cases, messages=messages, ) @route("/string/<prjname>/<lngname>/<sname>", method="GET") @protected(["string", "prjname", "lngname"]) def str_form(userauth, prjname, lngname, sname): parms = check_page_parameters(prjname, lngname, sname) if parms is None: return pmd, bchg, lng, binfo = parms return output_string_edit_page(userauth, bchg, binfo, lng, pmd, lngname, sname, None) @route("/string/<prjname>/<lngname>/<sname>", method="POST") @protected(["string", "prjname", "lngname"]) def str_post(userauth, prjname, lngname, sname): parms = check_page_parameters(prjname, lngname, sname) if parms is None: return pmd, bchg, lng, binfo = parms request.forms.recode_unicode = False # Allow Unicode input request_forms = request.forms.decode() # Convert dict to Unicode. base_str = request_forms.get( "base") # Base text translated against in the form. if base_str is None or base_str != bchg.base_text.text: abort( 404, "Base language has been changed, please translate the newer version instead" ) return # Get changes against bchg case_chgs = data.get_all_changes(lng.changes.get(sname), lng.case, bchg) projtype = pmd.pdata.projtype stamp = None # Assigned a stamp object when a change is made in the translation. # Collected output data new_changes = [] new_state_errors = {} for case in lng.case: trl_str = request_forms.get("text_" + case) # Translation text in the form. if trl_str is None: continue # It's missing from the form data. trl_str = language_file.sanitize_text(trl_str) if case == "" and trl_str == "" and bchg.base_text != "": # Empty base case for a non-empty string, was the "allow empty base translation" flag set? if request_forms.get("allow_empty_default") != "on": if stamp is None: stamp = data.make_stamp() txt = data.Text(trl_str, case, stamp) tchg = data.Change(sname, case, bchg.base_text, txt, stamp, userauth.name) error = language_file.ErrorMessage( language_file.ERROR, None, "Empty default case is not allowed (enable by setting 'Allow empty input').", ) new_state_errors[case] = (tchg, data.INVALID, [error]) continue # Check whether there is a match with a change in the translation. trl_chg = None for cchg in case_chgs[case]: if cchg.new_text.text == trl_str: trl_chg = cchg break if trl_chg is None: if case != "" and trl_str == "" and len(case_chgs[case]) == 0: continue # Skip adding empty non-base cases if there are no strings. # A new translation against bchg! if stamp is None: stamp = data.make_stamp() txt = data.Text(trl_str, case, stamp) tchg = data.Change(sname, case, bchg.base_text, txt, stamp, userauth.name) state, errors = data.get_string_status(projtype, tchg, case, lng, bchg.base_text, binfo) if state == data.MISSING or state == data.INVALID: new_state_errors[case] = (tchg, state, errors) else: new_changes.append(tchg) continue
def create_statistics(self, parm_lng = None): """ Construct overview statistics of the project. @param parm_lng: If specified only update the provided language. Otherwise, update all translations. @type parm_lng: C{Language} or C{None} """ pdata = self.pdata blng = pdata.get_base_language() if blng is None: return self.blang_name = blng.name self.blang_count = len(blng.changes) if parm_lng is None or parm_lng is blng: # Update all languages. pdata.statistics = {} # Get the pdata.statistics[lname] map for the base language. bstat = pdata.statistics.get(pdata.base_language) if bstat is None: bstat = {} pdata.statistics[pdata.base_language] = bstat projtype = pdata.projtype # First construct detailed information in the project for sname, bchgs in blng.changes.items(): # Check newest base language string. bchg = data.get_newest_change(bchgs, '') binfo = language_file.check_string(projtype, bchg.base_text.text, True, None, blng, True) if binfo.has_error: bstat[sname] = [('', data.INVALID)] else: bstat[sname] = [('', data.UP_TO_DATE)] if parm_lng is None or parm_lng is blng: # Update all languages. lngs = pdata.languages.items() else: lngs = [(parm_lng.name, parm_lng)] # Update just 'parm_lng' for lname, lng in lngs: assert projtype.allow_case or lng.case == [''] if lng is blng: continue # Get the pdata.statistics[lname][sname] list. lstat = pdata.statistics.get(lname) if lstat is None: lstat = {} pdata.statistics[lname] = lstat sstat = lstat.get(sname) if sstat is None: sstat = [] lstat[sname] = sstat if binfo is None: # Base string is broken, cannot judge translations. sstat[:] = [('', data.UNKNOWN)] continue chgs = lng.changes.get(sname) if chgs is None: # No translation at all sstat[:] = [('', data.MISSING)] continue chgs = data.get_all_newest_changes(chgs, lng.case) detailed_state = data.decide_all_string_status(projtype, bchg, chgs, lng, binfo) sstat[:] = sorted((c,se[0]) for c, se in detailed_state.items()) # Construct overview statistics for each language. if parm_lng is None or parm_lng is blng: # Update all languages. lngs = pdata.languages.items() self.overview = {} else: lngs = [(parm_lng.name, parm_lng)] # Update just 'parm_lng' for lname, lng in lngs: #if lng is blng: continue counts = [ 0 for i in range(data.MAX_STATE) ] for sname in blng.changes: state = max(s[1] for s in pdata.statistics[lname][sname]) if state != data.MISSING_OK: counts[state] = counts[state] + 1 self.overview[lname] = counts