def project(userauth, prjname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata base_lng = pdata.get_base_language() transl = [] bcounts = None if base_lng is not None: for lname, lng in pdata.languages.items(): if lng is base_lng: continue transl.append((lng, pmd.overview.get(lname))) transl.sort(key=lambda x: x[0].name) bcounts = pmd.overview.get(base_lng.name) return template('project', userauth=userauth, pmd=pmd, transl=transl, base_lng=base_lng, bcounts=bcounts)
def download_list(userauth, prjname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata response.content_type = 'text/plain; charset=UTF-8' lines = ['isocode,base_isocode,changetime'] for lng, ldata in pdata.languages.items(): tstamp = get_newest_change(ldata) if tstamp is None: text = "--no-time-available--" else: text = data.encode_stamp(tstamp) if pdata.base_language == lng: parent_lng = '' else: parent_lng = pdata.base_language line = "{},{},{}".format(ldata.name, parent_lng, text) lines.append(line) return '\n'.join(lines) + '\n'
def project_get(userauth, prjname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return return template("projsettings", userauth=userauth, pmd=pmd)
def create_project(userauth, prjtypename, prjname): acceptance = utils.verify_name(prjname, "Project identifier", True) if acceptance is not None: redirect("/newproject", message=acceptance) return if prjname in config.cache.projects: redirect("/newproject", message='Project "{}" already exists'.format(prjname)) return human_name = request.forms.humanname.strip() acceptance = utils.verify_name(human_name, "Full project name", False) if acceptance is not None: redirect("/newproject", message=acceptance) return url = request.forms.url acceptance = utils.verify_url(url) if acceptance is not None: redirect("/newproject", message=acceptance) return projtype = project_type.project_types.get(prjtypename) if projtype is None or projtype.name not in config.cfg.project_types: abort(404, "Unknown project type.") return error = config.cache.create_project(prjname, human_name, projtype, url) if error is not None: abort(404, error) return message = "Successfully created project '" + prjname + "' " + utils.get_datetime_now_formatted() redirect("/project/<prjname>", prjname=prjname.lower(), message=message)
def wrapper(*a, **ka): pname = [ka.get(p, p) for p in page_name] + [METHODS.get(request.method, "-")] prjname = ka.get("prjname") lngname = ka.get("lngname") userauth = get_session() # We must read all uploaded content before returning a response. # Otherwise the connection may be closed by the server and the client aborts. for f in request.files: pass if userauth is None: # No authentication backend. abort(403, "Access denied") elif userauth.may_access(pname, prjname, lngname): # Access granted. return func(userauth, *a, **ka) elif not userauth.is_auth: # Not logged in. redirect("/login", redirect=request.path) elif prjname is not None and prjname in config.cache.projects: # Valid user, but insufficient permissions: Go to project page. redirect("/project/<prjname>", prjname=prjname.lower(), message="Access denied") else: # Valid user, no project context: Go to project list. redirect("/projects", message="Access denied") return
def new_language_get(userauth, prjname): """ Form to add another language to the project. """ pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata base_langs = [] translations = [] can_be_added = [] for lang in pdata.get_all_languages(): if lang.isocode == pdata.base_language: base_langs.append(lang) continue if lang.isocode in pdata.languages: translations.append(lang) continue can_be_added.append(lang) translations.sort(key=lambda x: x.isocode) can_be_added.sort(key=lambda x: x.isocode) return template( "newlanguage", userauth=userauth, pmd=pmd, base_langs=base_langs, translations=translations, can_be_added=can_be_added, )
def language(userauth, lngname): """ Get an overview of the state of the given language in every project. @param lngname: Name of the language (isocode). @type lngname: C{str} """ lnginfo = language_info.isocode.get(lngname) if lnginfo is None: abort(404, "Language is unknown") return prjdata = [] for pmd in config.cache.projects.values(): lstate = pmd.overview.get(lngname) if lstate is not None: prjdata.append((pmd, True, lstate)) else: lstate = [0 for i in range(data.MAX_STATE)] lstate[data.MISSING] = pmd.blang_count prjdata.append((pmd, False, lstate)) prjdata.sort(key=lambda p: p[0].human_name.lower()) return template('language', userauth=userauth, lnginfo=lnginfo, prjdata=prjdata)
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
def check_page_parameters(prjname, lngname, sname): """ Check whether the parameters make any sense and abort if not, else return the derived project data. @param prjname: Name of the project. @type prjname: C{str} @param lngname: Name of the language. @type lngname: C{str} @param sname: Name of the string. @type sname: C{str} @return: Nothing if the parameters don't make sense, else the project meta data, base change, language, and base text info. @rtype: C{None} or tuple (L{ProjectMetaData}, L{Change}, L{Language}, L{StringInfo} """ pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return None pdata = pmd.pdata lng = pdata.languages.get(lngname) if lng is None: abort(404, "Language does not exist in the project") return None assert pdata.projtype.allow_case or lng.case == [""] blng = pdata.get_base_language() if blng == lng: abort(404, "Language is not a translation") return None bchgs = blng.changes.get(sname) if bchgs is None or len(bchgs) == 0: abort(404, "String does not exist in the project") return None # Check newest base language string. bchg = max(bchgs) binfo = language_file.check_string(pdata.projtype, bchg.base_text.text, True, None, blng, True) if binfo.has_error: # XXX Add errors too abort( 404, "String cannot be translated, its base language version is incorrect" ) return None return pmd, bchg, lng, binfo
def page_get_subdir(userauth, prjname, lngname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return linfo = language_info.isocode.get(lngname) if linfo is None: abort(404, "Language is unknown") return return template("upload_lang_subdir", userauth=userauth, pmd=pmd, lnginfo=linfo)
def page_get(userauth, prjname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata if pdata.projtype.has_grflangid: return template('upload_lang', userauth=userauth, pmd=pmd) else: return template('upload_lang_select', userauth=userauth, pmd=pmd, lnginfos=sorted(pdata.get_all_languages(), lambda l: l.isocode))
def project_post(userauth, prjname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata # Get and check the new project name. human_name = request.forms.name.strip() acceptance = utils.verify_name(human_name, "Full project name", False) if acceptance is not None: redirect("/projsettings/<prjname>", prjname=prjname, message=acceptance) return # Get and check the new url. url = request.forms.url acceptance = utils.verify_url(url) if acceptance is not None: abort(404, acceptance) message_parts = [] if pdata.human_name != human_name: pdata.human_name = human_name pmd.human_name = human_name # Also assign the new name to the meta-data storage. message_parts.append("name") if pdata.url != url: pdata.url = url message_parts.append("URL") if len(message_parts) == 0: message = "Project settings are not modified" else: parts = " and ".join(message_parts) if len(message_parts) < 2: has = "has" else: has = "have" message = "Project {} {} been changed {}" message = message.format(parts, has, utils.get_datetime_now_formatted()) config.cache.save_pmd(pmd) redirect("/project/<prjname>", prjname=prjname.lower(), message=message)
def page_post(userauth, prjname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata if not pdata.projtype.has_grflangid: abort(404, "No language identification provided.") return langfile = request.files.langfile override = request.forms.override is_base = request.forms.base_language return handle_upload(userauth, pmd, prjname, langfile, override, is_base, None)
def page_post_subdir(userauth, prjname, lngname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return linfo = language_info.isocode.get(lngname) if linfo is None: abort(404, "Language is unknown") return langfile = request.files.langfile override = request.forms.override is_base = request.forms.base_language return handle_upload(userauth, pmd, prjname, langfile, override, is_base, linfo)
def annotate(userauth, prjname, lngname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return pdata = pmd.pdata base_lng = pdata.get_base_language() lng = pdata.languages.get(lngname) if lng is None: abort(404, "Language does not exist in the project") return response.content_type = "text/plain; charset=UTF-8" return make_langfile(pdata, base_lng, lng, annotated_langfile)
def new_language_post(userauth, prjname): """ Construct the requested language. """ pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return new_iso = request.forms.language_select lng_def = get_language(new_iso) if lng_def is None: msg = "No language found that can be created" abort(404, msg) return return template("makelanguage", userauth=userauth, pmd=pmd, lnginfo=lng_def)
def fix_string(userauth, prjname, lngname): """ Fix a random string. @param prjname: Name of the project. @type prjname: C{str} @param lngname: Language name. @type lngname: C{str} """ 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 blng = pdata.get_base_language() if blng == lng: abort(404, "Language is not a translation") return return fix_string_page(pmd, prjname, lngname, None)
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 delete_submit(userauth, prjname, lngname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return if pmd.storage_type == config.STORAGE_SEPARATE_LANGUAGES: abort( 404, "Cannot delete a language, ask the system administrator to remove the language file" ) return pdata = pmd.pdata lng = pdata.languages.get(lngname) if lng is None: abort(404, "Language does not exist in the project") return if pdata.base_language == lngname: abort(404, "Cannot delete base language!") return del pdata.languages[lngname] pdata.set_modified() if lngname in pdata.statistics: del pdata.statistics[lngname] if lngname in pmd.overview: del pmd.overview[lngname] config.process_project_changes(pdata) # Update changes of the project. config.cache.save_pmd(pmd) msg = "Language " + lngname + " is deleted" redirect("/project/<prjname>", prjname=prjname, message=msg) return
def delete_form(userauth, prjname, lngname): pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return if pmd.storage_type == config.STORAGE_SEPARATE_LANGUAGES: abort( 404, "Cannot delete a language, ask the system administrator to remove the language file" ) return pdata = pmd.pdata lng = pdata.languages.get(lngname) if lng is None: abort(404, "Language does not exist in the project") return if pdata.base_language == lngname: abort(404, "Cannot delete base language!") return return template("delete_translation", userauth=userauth, pmd=pmd, lng=lng)
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 make_langfile(pdata, base_lng, lng, add_func): """ Construct a language file. @param pdata: Project data. @type pdata: L{Project} @param base_lng: Base language. @type base_lng: L{Language} @param lng: Language to output. @type lng: L{Language} @param add_func: Function to add text to the output. @type add_func: C{function} @return: Text containing the language file. @rtype: C{str} """ projtype = pdata.projtype if projtype.allow_case: lng_case = lng.case else: lng_case = [ "" ] # Suppress writing of non-default cases if the project doesn't allow them. sdict = pdata.statistics sdict = sdict.get(lng.name) # Statistics dict for the queried language. if sdict is None: abort(404, "Missing language statistics") return lines = [] for skel_type, skel_value in pdata.skeleton: if skel_type == "literal": add_func(lines, skel_type, skel_value) continue if skel_type == "string": column, sname = skel_value chgs = lng.changes.get(sname) if chgs is not None: cstates = sdict[sname] # Language has sorted cases, thus the default case comes first. for case in lng_case: chg = data.get_newest_change(chgs, case) if chg is not None: if case == "": line = sname else: line = sname + "." + case if lng == base_lng: text = chg.base_text.text else: text = chg.new_text.text if case != "" and text == "": # Suppress printing of empty non-default cases in translations. continue cstate = next(se for c, se in cstates if c == case) if cstate == data.INVALID: # Suppress invalid translations. continue length = column - len(line) if length < 0: length = 0 add_func(lines, skel_type, line + (" " * length) + ":" + text) if chg.user is not None: add_func(lines, "credits", chg.user) continue if projtype.has_grflangid and skel_type == "grflangid": add_func(lines, skel_type, "##grflangid 0x{:02x}".format(lng.grflangid)) continue if skel_type == "plural": if lng.plural is not None: add_func(lines, skel_type, "##plural {:d}".format(lng.plural)) continue if skel_type == "case": cases = [c for c in lng.case if c != ""] if projtype.allow_case and len(cases) > 0: add_func(lines, skel_type, "##case " + " ".join(cases)) continue if skel_type == "gender": if projtype.allow_gender and len(lng.gender) > 0: add_func(lines, skel_type, "##gender " + " ".join(lng.gender)) continue if skel_type == "pragma": content = lng.custom_pragmas.get(skel_value) if content is not None: add_func(lines, skel_type, content) continue
def make_language_post(userauth, prjname, lngname): """ Create the requested language. @param prjname: Name of the project. @type prjname: C{str} @param lngname: Name of the language to create in the project. @type lngname: C{str} """ pmd = config.cache.get_pmd(prjname) if pmd is None: abort(404, "Project does not exist") return lng_def = get_language(lngname) if lng_def is None: msg = "No language found that can be created" abort(404, msg) return pdata = pmd.pdata if lng_def.isocode in pdata.languages: abort(404, 'Language "{}" already exists'.format(lng_def.isocode)) return projtype = pdata.projtype if not projtype.allow_language(lng_def): msg = 'Language "{}" may not be created in this project'.format( lng_def.isocode) abort(404, msg) return # Create the language. lng = data.Language(lng_def.isocode) lng.grflangid = lng_def.grflangid lng.plural = lng_def.plural if projtype.allow_gender: lng.gender = lng_def.gender else: lng.gender = [] if projtype.allow_case: lng.case = lng_def.case else: lng.case = [""] pdata.languages[lng.name] = lng pdata.set_modified() lng.set_modified() config.cache.save_pmd(pmd) pmd.create_statistics(lng) msg = "Successfully created language '" + lng.name + "' " + utils.get_datetime_now_formatted( ) redirect("/translation/<prjname>/<lngname>", prjname=prjname, lngname=lng.name, message=msg) return