Example #1
0
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
Example #2
0
def process_project_changes(pdata):
    """
    Update the changes of the texts in the project.

    @param pdata: Project data to examine and change.
    @type  pdata: L{Project}

    @return: Changes were changed.
    @rtype:  C{bool}
    """
    used_basetexts = set()
    stamp = data.make_stamp()
    modified = False
    if pdata.base_language is None:
        return False  # No base language -> nothing to do.

    # Update translation changes.
    for lname, lng in pdata.languages.items():
        if lname == pdata.base_language:
            continue
        lng_modified = False
        for chgs in lng.changes.values():
            nchgs = process_changes(chgs, lng.case, stamp, used_basetexts)
            if len(nchgs) != len(chgs):
                chgs[:] = nchgs
                modified = True
                lng_modified = True

        if lng_modified:
            lng.set_modified()

    # Update base language changes.
    blng = pdata.languages[pdata.base_language]
    blng_modified = False
    for chgs in blng.changes.values():
        chgs.sort()
        nchgs = []
        changed = False
        first = True
        for chg in reversed(chgs):
            if first or chg.base_text in used_basetexts:
                nchgs.append(chg)
                first = False
            else:
                changed = True

        if changed:
            chgs[:] = nchgs
            modified = True
            pdata.flush_related_cache()
            blng_modified = True

    if blng_modified:
        blng.set_modified()

    return modified
Example #3
0
def get_datetime_now_formatted():
    """
    Output a string denoting 'now' in time,
    useful for user messages when creating or updating objects

    @return: A string denoting 'now' in time (in UTC).
    @rtype:  C{str}
    """
    stamp = data.make_stamp()
    return str(stamp)
Example #4
0
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)
Example #5
0
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
Example #6
0
            continue

        # Found a match with an existing translation text
        if case_chgs[case][-1] == trl_chg:
            # Latest translation.
            if trl_chg.base_text == bchg.base_text:  # And also the latest base language!
                continue
            state, _errors = data.get_string_status(projtype, trl_chg, case,
                                                    lng, bchg.base_text, binfo)
            if state == data.OUT_OF_DATE:
                # We displayed a 'this string is correct' checkbox. Was it changed?
                if request_forms.get("ok_" + case):
                    # Move to latest base language text.
                    if stamp is None:
                        stamp = data.make_stamp()
                    trl_chg.base_text = bchg.base_text
                    trl_chg.stamp = stamp
                    trl_chg.user = userauth.name
            continue

        # We got an older translation instead.
        if stamp is None:
            stamp = data.make_stamp()
        tchg = data.Change(sname, case, bchg.base_text, trl_chg.new_text,
                           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:
Example #7
0
                new_changes.append(tchg)

            continue

        # Found a match with an existing translation text
        if case_chgs[case][-1] == trl_chg:
            # Latest translation.
            if trl_chg.base_text == bchg.base_text:  # And also the latest base language!
                continue
            state, _errors = data.get_string_status(projtype, trl_chg, case,
                                                    lng, bchg.base_text, binfo)
            if state == data.OUT_OF_DATE:
                # We displayed a 'this string is correct' checkbox. Was it changed?
                if request_forms.get('ok_' + case):
                    # Move to latest base language text.
                    if stamp is None: stamp = data.make_stamp()
                    trl_chg.base_text = bchg.base_text
                    trl_chg.stamp = stamp
                    trl_chg.user = userauth.name
            continue

        # We got an older translation instead.
        if stamp is None: stamp = data.make_stamp()
        tchg = data.Change(sname, case, bchg.base_text, trl_chg.new_text,
                           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)