Skip to content

Data Models & Types

To ensure type safety and consistency across the bot, OSCAR uses strict data models. These define how data flows from the Nextcloud API (TablesModuleDict) into the application logic (Module class) and finally into the database (ModuleDict).

1. The Module Class

This class is the core wrapper around university module data. It provides:

  • Abstraction: Unifies access to attributes regardless of the source (API or DB).
  • Localization: Implements "Smart Getters" (e.g., get_title, get_content) that return English translations if available, or fall back to on-the-fly machine translation via the translate library.
  • Transformation: Converts the chaotic Nextcloud API keys (e.g., "Modultitel (englisch)") into clean Python attributes (title_en).

src.util.module.Module

Module(id_: int, language: ModuleLanguage, status: int = -1, title: str = '', title_en: str = '', chair: int = -1, responsibility: str = '', lecturer: str = '', abbreviation: str = '', credit_points: str = '', semester_position: int = -1, academic_semester: int = -1, duration: int = -1, level: list[int] | None = None, learning_goals: str = '', content: str = '', workload: str = '', study_exam_type: str = '', teaching_form_sws: str = '', requirements_by_exam_regulations: str = '', recommended_prerequisites: str = '', media_forms: str = '', literature: str = '', notes: str = '', first_examiner: str = '', second_examiner: str = '', substitute: str = '', usability_bsc_inf: list[int] | None = None, usability_bsc_cv: list[int] | None = None, usability_bsc_inginf: list[int] | None = None, usability_bsc_wif: list[int] | None = None, usability_bsc_inf_bilingual: list[int] | None = None, usability_msc_inf: list[int] | None = None, usability_msc_inginf: list[int] | None = None, usability_msc_wif: list[int] | None = None, usability_msc_dke: list[int] | None = None, usability_msc_de: list[int] | None = None, usability_msc_vc: list[int] | None = None, subtitle: str = '', exported_to: list[int] | None = None, imported_from: str = '', released_on: str = '', courses: str = '', irregular: str = '', exam_prerequisite: str = '', translation: Module | None = None)

Class for storing modules in a structured way.

Source code in src/util/module.py
    def __init__(
		self,
        id_: int,                  # Identifizierung
        language: ModuleLanguage,  # Modulsprache

        status: int = -1,                                     # Status
        title: str = "",                                      # Modultitel
        title_en: str = "",                                   # Modultitel (englisch)
        chair: int = -1,                                      # Lehrstuhl
        responsibility: str = "",                             # Modulverantwortung
        lecturer: str = "",                                   # Dozent:in
        abbreviation: str = "",                               # Modulkürzel
        credit_points: str = "",                              # Credit Points
        semester_position: int = -1,                          # Semesterlage
        academic_semester: int = -1,                          # Fachsemester
        duration: int = -1,                                   # Dauer
        level: list[int] | None = None,                       # Niveau
        learning_goals: str = "",                             # Angestrebte Lernergebnisse
        content: str = "",                                    # Inhalt
        workload: str = "",                                   # Arbeitsaufwand
        study_exam_type: str = "",                            # Studien-/Prüfungsleistung
        teaching_form_sws: str = "",                          # Lehrform / SWS
        requirements_by_exam_regulations: str = "",           # Voraussetzungen nach Prüfungsordnung
        recommended_prerequisites: str = "",                  # Empfohlene Voraussetzungen
        media_forms: str = "",                                # Medienformen
        literature: str = "",                                 # Literatur
        notes: str = "",                                      # Hinweise
        first_examiner: str = "",                             # Erstprüfer
        second_examiner: str = "",                            # Zweitprüfer
        substitute: str = "",                                 # Stellvertreter
        usability_bsc_inf: list[int] | None = None,           # Verwendbarkeit B.Sc. INF
        usability_bsc_cv: list[int] | None = None,            # Verwendbarkeit B.Sc. CV
        usability_bsc_inginf: list[int] | None = None,        # Verwendbarkeit B.Sc. INGINF
        usability_bsc_wif: list[int] | None = None,           # Verwendbarkeit B.Sc. WIF
        usability_bsc_inf_bilingual: list[int] | None = None, # Verwendbarkeit B.Sc. INF (bilingual
        usability_msc_inf: list[int] | None = None,           # Verwendbarkeit M.Sc. INF
        usability_msc_inginf: list[int] | None = None,        # Verwendbarkeit M.Sc. INGINF
        usability_msc_wif: list[int] | None = None,           # Verwendbarkeit M.Sc. WIF
        usability_msc_dke: list[int] | None = None,           # Verwendbarkeit M.Sc. DKE
        usability_msc_de: list[int] | None = None,            # Verwendbarkeit M.Sc. DE
        usability_msc_vc: list[int] | None = None,            # Verwendbarkeit M.Sc. VC
        subtitle: str = "",                                   # Moduluntertitel
        exported_to: list[int] | None = None,                 # Exportiert nach
        imported_from: str = "",                              # Importiert von
        released_on: str = "",                                # Freigabe am
        courses: str = "",                                    # Lehrveranstaltungen
        irregular: str = "",                                  # Unregelmäßig
        exam_prerequisite: str = "",                          # Prüfungsvorleistung
        translation: 'Module | None' = None
		) -> None:

        self.id_: int = id_
        self.language: ModuleLanguage = language

        self._status: int = status
        self._title: str = title
        self._title_en: str = title_en
        self._chair: int = chair
        self.responsibility: str = responsibility
        self.lecturer: str = lecturer
        self.abbreviation: str = abbreviation
        self.credit_points: str = credit_points
        self._semester_position: int = semester_position
        self._academic_semester: int = academic_semester
        self._duration: int = duration
        self._level: list[int] = [] if level is None else level
        self._learning_goals: str = learning_goals
        self._content: str = content
        self._workload: str = workload
        self._study_exam_type: str = study_exam_type
        self._teaching_form_sws: str = teaching_form_sws
        self._requirements_by_exam_regulations: str = requirements_by_exam_regulations
        self._recommended_prerequisites: str = recommended_prerequisites
        self._media_forms: str = media_forms
        self._literature: str = literature
        self._notes: str = notes
        self.first_examiner: str = first_examiner
        self.second_examiner: str = second_examiner
        self.substitute: str = substitute
        self._usability_bsc_inf: list[int] = [] if usability_bsc_inf is None else usability_bsc_inf
        self._usability_bsc_cv: list[int] = [] if usability_bsc_cv is None else usability_bsc_cv
        self._usability_bsc_inginf: list[int] = [] if usability_bsc_inginf is None \
            else usability_bsc_inginf
        self._usability_bsc_wif: list[int] = [] if usability_bsc_wif is None else usability_bsc_wif
        self._usability_bsc_inf_bilingual: list[int] = [] if usability_bsc_inf_bilingual is None \
            else usability_bsc_inf_bilingual
        self._usability_msc_inf: list[int] = [] if usability_msc_inf is None else usability_msc_inf
        self._usability_msc_inginf: list[int] = [] if usability_msc_inginf is None \
            else usability_msc_inginf
        self._usability_msc_wif: list[int] = [] if usability_msc_wif is None else usability_msc_wif
        self._usability_msc_dke: list[int] = [] if usability_msc_dke is None else usability_msc_dke
        self._usability_msc_de: list[int] = [] if usability_msc_de is None else usability_msc_de
        self._usability_msc_vc: list[int] = [] if usability_msc_vc is None else usability_msc_vc
        self._subtitle: str = subtitle
        self._exported_to: list[int] = [] if exported_to is None else exported_to
        self._imported_from: str = imported_from
        self.released_on: str = released_on
        self._courses: str = courses
        self._irregular: str = irregular
        self._exam_prerequisite: str = exam_prerequisite
        self.translation: Module | None = translation

from_name classmethod

from_name(name: str, name_is_en: bool = False) -> Self

Creates a Module instance by searching for a module with the given name.

Parameters:

Name Type Description Default
name str

The module title to search for

required
name_is_en bool

If True, search in English titles, otherwise in German titles

False

Returns:

Type Description
Self

The first matching Module instance

Raises:

Type Description
IndexError

If no module with the given name is found

Source code in src/util/module.py
@classmethod
def from_name(cls, name: str, name_is_en:bool = False) -> Self:
    """Creates a Module instance by searching for a module with the given name.

        Parameters:
            name: The module title to search for
            name_is_en: If True, search in English titles, otherwise in German titles

        Returns:
            The first matching Module instance

        Raises:
            IndexError: If no module with the given name is found
    """
    modules: list[TablesModuleDict] = get_rows(720)
    search_field: str = "Modultitel (englisch)" if name_is_en else "Modultitel"

    filtered_module_ids: list[int] = [
        module["Identifizierung"] for module in modules
        if module.get(search_field, "") == name
    ]
    # dict.fromkeys drops duplicates but keeps the table order, so a title
    # shared by several modules always resolves to the same one
    filtered_module_ids = list(dict.fromkeys(filtered_module_ids))

    if not filtered_module_ids:
        raise IndexError(f"No module found with '{search_field}': '{name}'")
    if len(filtered_module_ids) > 1:
        # pylint: disable=C0301 (line-too-long)
        logger.warning(f"multiple modules modules found for with '{search_field}': '{name}' (selecting the module with id={filtered_module_ids[0]})")

    return cls.from_id(filtered_module_ids[0])

from_tables_dict classmethod

from_tables_dict(data: TablesModuleDict, trans_data: TablesModuleDict | None = None) -> Self

Creates a Module instance from a dictionary as provided by the tables (nextcloud) api.

Parameters:

Name Type Description Default
data TablesModuleDict

A dictionary with the module data.

required
trans_data TablesModuleDict | None

An optional dictionary with the (translated) module data.

None

for structure see

Source code in src/util/module.py
@classmethod
def from_tables_dict(
    cls,
    data: TablesModuleDict,
    trans_data: TablesModuleDict|None = None
) -> Self:
    """ Creates a Module instance from a dictionary as provided by the tables (nextcloud) api.

        Parameters:
            data: A dictionary with the module data.
            trans_data: An optional dictionary with the (translated) module data.

        for structure see <TablesModuleDict>

    """

    data = without_nulls(data)  # pyright: ignore[reportAssignmentType]
    if trans_data is not None:
        trans_data = without_nulls(trans_data)  # pyright: ignore[reportAssignmentType]

    id_: int | None = data.get("Identifizierung", None)
    module_language: int | None = data.get("Modulsprache", None)
    trans: Module | None = None

    assert id_ is not None, "An identification number is required."

    language: ModuleLanguage
    if module_language in ModuleLanguage.values():
        language = ModuleLanguage(module_language)
        if trans_data:
            trans = Module.from_tables_dict(trans_data)
    else:
        logger.warning(
            f"unknown language id: {module_language}, assuming english ({ModuleLanguage.EN})"
        )
        language = ModuleLanguage.EN

    return cls(
        id_      = id_,
        language = language,

        status                           = data.get("Status", -1),
        title                            = data.get("Modultitel", ""),
        title_en                         = data.get("Modultitel (englisch)", ""),
        chair                            = data.get("Lehrstuhl", -1),
        responsibility                   = data.get("Modulverantwortung", ""),
        lecturer                         = data.get("Dozent:in", ""),
        abbreviation                     = data.get("Modulkürzel", ""),
        credit_points                    = data.get("Credit Points", ""),
        semester_position                = data.get("Semesterlage", -1),
        academic_semester                = data.get("Fachsemester", -1),
        duration                         = data.get("Dauer", -1),
        level                            = data.get("Niveau", []),
        learning_goals                   = data.get("Angestrebte Lernergebnisse", ""),
        content                          = data.get("Inhalt", ""),
        workload                         = data.get("Arbeitsaufwand", ""),
        study_exam_type                  = data.get("Studien-/Prüfungsleistung", ""),
        teaching_form_sws                = data.get("Lehrform / SWS", ""),
        requirements_by_exam_regulations = data.get("Voraussetzungen nach Prüfungsordnung", ""),
        recommended_prerequisites        = data.get("Empfohlene Voraussetzungen", ""),
        media_forms                      = data.get("Medienformen", ""),
        literature                       = data.get("Literatur", ""),
        notes                            = data.get("Hinweise", ""),
        first_examiner                   = data.get("Erstprüfer", ""),
        second_examiner                  = data.get("Zweitprüfer", ""),
        substitute                       = data.get("Stellvertreter", ""),
        usability_bsc_inf                = data.get("Verwendbarkeit B.Sc. INF", []),
        usability_bsc_cv                 = data.get("Verwendbarkeit B.Sc. CV", []),
        usability_bsc_inginf             = data.get("Verwendbarkeit B.Sc. INGINF", []),
        usability_bsc_wif                = data.get("Verwendbarkeit B.Sc. WIF", []),
        usability_bsc_inf_bilingual      = data.get("Verwendbarkeit B.Sc. INF (bilingual)", []),
        usability_msc_inf                = data.get("Verwendbarkeit M.Sc. INF", []),
        usability_msc_inginf             = data.get("Verwendbarkeit M.Sc. INGINF", []),
        usability_msc_wif                = data.get("Verwendbarkeit M.Sc. WIF", []),
        usability_msc_dke                = data.get("Verwendbarkeit M.Sc. DKE", []),
        usability_msc_de                 = data.get("Verwendbarkeit M.Sc. DE", []),
        usability_msc_vc                 = data.get("Verwendbarkeit M.Sc. VC", []),
        subtitle                         = data.get("Moduluntertitel", ""),
        exported_to                      = data.get("Exportiert nach", []),
        imported_from                    = data.get("Importiert von", ""),
        released_on                      = data.get("Freigabe am", ""),
        courses                          = data.get("Lehrveranstaltungen", ""),
        irregular                        = data.get("Unregelmäßig", ""),
        exam_prerequisite                = data.get("Prüfungsvorleistung", ""),
        translation                      = trans,
    )

from_dict classmethod

from_dict(data: ModuleDict, trans_data: ModuleDict | None = None) -> Self

Creates a Module instance from a dictionary.

Parameters:

Name Type Description Default
data ModuleDict

A dictionary with the module data.

required
trans_data ModuleDict | None

An optional dictionary with the (translated) module data.

None

for structure see

Source code in src/util/module.py
@classmethod
def from_dict(cls, data: ModuleDict, trans_data: ModuleDict|None = None) -> Self:
    """ Creates a Module instance from a dictionary.

        Parameters:
            data: A dictionary with the module data.
            trans_data: An optional dictionary with the (translated) module data.

        for structure see <ModuleDict>

    """
    data = without_nulls(data)  # pyright: ignore[reportAssignmentType]
    if trans_data is not None:
        trans_data = without_nulls(trans_data)  # pyright: ignore[reportAssignmentType]

    id_: int | None = data.get("id", None)
    language_input = data.get("language", "")
    # the language is stored as its id, but may arrive as the string of that id
    language: ModuleLanguage = ModuleLanguage(int(language_input))

    assert id_ is not None, "An identification number is required."

    trans: Module | None = None
    if trans_data:
        trans = Module.from_dict(trans_data)

    return cls(
        id_       = id_,
        language  = language,


        status                           = data.get("status", -1),
        title                            = data.get("title", ""),
        title_en                         = data.get("title_en", ""),
        chair                            = data.get("chair", -1),
        responsibility                   = data.get("responsibility", ""),
        lecturer                         = data.get("lecturer", ""),
        abbreviation                     = data.get("abbreviation", ""),
        credit_points                    = data.get("credit_points", ""),
        semester_position                = data.get("semester_position", -1),
        academic_semester                = data.get("academic_semester", -1),
        duration                         = data.get("duration", -1),
        level                            = data.get("level", []),
        learning_goals                   = data.get("learning_goals", ""),
        content                          = data.get("content", ""),
        workload                         = data.get("workload", ""),
        study_exam_type                  = data.get("study_exam_type", ""),
        teaching_form_sws                = data.get("teaching_form_sws", ""),
        requirements_by_exam_regulations = data.get("requirements_by_exam_regulations", ""),
        recommended_prerequisites        = data.get("recommended_prerequisites", ""),
        media_forms                      = data.get("media_forms", ""),
        literature                       = data.get("literature", ""),
        notes                            = data.get("notes", ""),
        first_examiner                   = data.get("first_examiner", ""),
        second_examiner                  = data.get("second_examiner", ""),
        substitute                       = data.get("substitute", ""),
        usability_bsc_inf                = data.get("usability_bsc_inf", []),
        usability_bsc_cv                 = data.get("usability_bsc_cv", []),
        usability_bsc_inginf             = data.get("usability_bsc_inginf", []),
        usability_bsc_wif                = data.get("usability_bsc_wif", []),
        usability_bsc_inf_bilingual      = data.get("usability_bsc_inf_bilingual", []),
        usability_msc_inf                = data.get("usability_msc_inf", []),
        usability_msc_inginf             = data.get("usability_msc_inginf", []),
        usability_msc_wif                = data.get("usability_msc_wif", []),
        usability_msc_dke                = data.get("usability_msc_dke", []),
        usability_msc_de                 = data.get("usability_msc_de", []),
        usability_msc_vc                 = data.get("usability_msc_vc", []),
        subtitle                         = data.get("subtitle", ""),
        exported_to                      = data.get("exported_to", []),
        imported_from                    = data.get("imported_from", ""),
        released_on                      = data.get("released_on", ""),
        courses                          = data.get("courses", ""),
        irregular                        = data.get("irregular", ""),
        exam_prerequisite                = data.get("exam_prerequisite", ""),
        translation                      = trans,
    )

get_title

get_title(lang: LanguageCode = LanguageCode.DE) -> str

Retrieves the modules title in the requested language.

Parameters:

Name Type Description Default
lang LanguageCode

The desired language as a LanguageCode enum value.

DE

Returns:

Type Description
str

A tuple containing the string and a boolean indicating whether

str

an automatic translation was needed

Source code in src/util/module.py
def get_title(self, lang: LanguageCode = LanguageCode.DE) -> str:
    """ Retrieves the modules title in the requested language.

        Parameters:
            lang: The desired language as a `LanguageCode` enum value.

        Returns:
            A tuple containing the string and a boolean indicating whether
            an automatic translation was needed
    """

    if lang == LanguageCode.DE:
        return self._title
    if lang == LanguageCode.EN:
        if self._title_en != "":
            return self._title_en

    return self._title

get_content

get_content(lang: LanguageCode = LanguageCode.DE) -> str

Retrieves the modules content in the requested language.

Parameters:

Name Type Description Default
lang LanguageCode

The desired language as a LanguageCode enum value.

DE

Returns:

Type Description
str

If possible the translated String

Source code in src/util/module.py
def get_content(self, lang: LanguageCode = LanguageCode.DE) -> str:
    """ Retrieves the modules content in the requested language.

        Parameters:
            lang: The desired language as a `LanguageCode` enum value.

        Returns:
            If possible the translated String
    """
    trans: str = ""
    if self.translation:
        trans = self.translation.get_content()
    return self._get_translation(
        self._content,
        trans,
        lang
    )

get_learning_goals

get_learning_goals(lang: LanguageCode = LanguageCode.DE) -> str

Retrieves the modules learning goals in the requested language.

Parameters:

Name Type Description Default
lang LanguageCode

The desired language as a LanguageCode enum value.

DE

Returns:

Type Description
str

If possible the translated String

Source code in src/util/module.py
def get_learning_goals(self, lang: LanguageCode = LanguageCode.DE) -> str:
    """ Retrieves the modules learning goals in the requested language.

        Parameters:
            lang: The desired language as a `LanguageCode` enum value.

        Returns:
            If possible the translated String
    """
    trans: str = ""
    if self.translation:
        trans = self.translation.get_learning_goals()
    return self._get_translation(
        self._learning_goals,
        trans,
        lang
    )

get_workload

get_workload(lang: LanguageCode = LanguageCode.DE) -> str

Retrieves the modules workload in the requested language.

Parameters:

Name Type Description Default
lang LanguageCode

The desired language as a LanguageCode enum value.

DE

Returns:

Type Description
str

If possible the translated String

Source code in src/util/module.py
def get_workload(self, lang: LanguageCode = LanguageCode.DE) -> str:
    """ Retrieves the modules workload in the requested language.

        Parameters:
            lang: The desired language as a `LanguageCode` enum value.

        Returns:
            If possible the translated String
    """
    trans: str = ""
    if self.translation:
        trans = self.translation.get_workload()
    return self._get_translation(
        self._workload,
        trans,
        lang
    )

get_usability_bsc_inf

get_usability_bsc_inf() -> list[str]

Trys to convert the usability b.sc. inf ids into human-readable strings.

Source code in src/util/module.py
def get_usability_bsc_inf(self) -> list[str]:
    """ Trys to convert the usability b.sc. inf ids into human-readable strings."""
    result: list[str] = []
    for usability in self._usability_bsc_inf:
        result.append(get_value_by_id(usability, "verwendbarkeit b.sc. inf"))
    return result

get_status

get_status() -> str

Trys to convert the status id into a human-readable string.

Source code in src/util/module.py
def get_status(self) -> str:
    """ Trys to convert the status id into a human-readable string."""
    return get_value_by_id(self._status, "status")

Factory Methods

from_id

Creates a Module instance by searching the cached catalogue for a specific ID.

  • Parameters:
    • id_ (int): The module identification number (e.g., 12345).
  • Returns:
    • Module: The populated instance.
  • Raises:
    • IndexError: If the ID is not found in the Nextcloud cache.

2. Type Definitions (TypedDicts)

We use TypedDict to Type-Hint dictionaries that don't warrant a full class.

Key Distinctions:

  • TablesModuleDict: Represents the raw JSON structure from the Nextcloud Tables API. Keys are German strings (e.g., "Credit Points").
  • ModuleDict: Represents the sanitized structure stored in the local SQLite database. Keys are English snake_case (e.g., credit_points).

This Module defines various typed dictionarys for representationg strucutured data.

ChallengeSubmissionDict

Bases: TypedDict

Defines the shape of a code golf challenge submission.

CohortStatsDict

Bases: TypedDict

Defines the aggregate cohort statistics shape.

FeedbackDict

Bases: TypedDict

Defines the shape of the provided Feedback data.

FeedbackReviewDict

Bases: TypedDict

Defines the shape of the stored Feedback review data.

LeaderboardEntryDict

Bases: TypedDict

Defines an entry on the code golf leaderboard.

MajorDistDict

Bases: TypedDict

Defines student distribution per study course.

ModuleDict

Bases: TypedDict

Defines the shape of the data of a Module.

ModuleRatingDict

Bases: TypedDict

Defines the summary shape of ratings for a module.

PrefsDict

Bases: TypedDict

Defines the shape of the Preference data.

TopModuleDict

Bases: TypedDict

Defines the shape of a popular module in cohort statistics.

UserDataDict

Bases: TypedDict

Defines the shape of everything the database stores about one student.

One entry per table that carries a discord id. The values are the stored rows themselves, not a prettier reading of them, because this is what a student gets handed out when they ask what we hold about them.

3. Core Enums

These Enums define the valid domain values for the application logic.

LanguageCode

Maps ISO 639 codes to integers for database storage.

  • EN = 0
  • DE = 1

src.util.enums.LanguageCode

Bases: IntEnum

Defines the supported languages (as its language code)

See ISO 639 (Set 1)

name property
name: str

The name of the Enum member (as lowercase).

from_language_code classmethod
from_language_code(language_code: str) -> Self

Creates a new enum based on the provided (ISO 639) language code

Source code in src/util/enums.py
@classmethod
def from_language_code(cls, language_code: str) -> Self:
    """ Creates a new enum based on the provided (ISO 639) language code"""
    return cls[language_code.upper()]
valid_languages classmethod
valid_languages() -> list[str]

Returns a list of all valid language codes

Source code in src/util/enums.py
@classmethod
def valid_languages(cls) -> list[str]:
    """ Returns a list of all valid language codes"""
    return [
        name.lower() for name in cls.__members__
    ]
values classmethod
values() -> list[int]

List of all valid enum values

Source code in src/util/enums.py
@classmethod
def values(cls) -> list[int]:
    """ List of all valid enum values"""
    return [
        member.value for member in cls
    ]

ModuleLanguage

The language a module is taught in, which is not the same thing as the language the bot speaks. The module database has a third value that LanguageCode cannot hold.

  • EN = 0
  • DE = 1
  • EN_DE = 2 — taught in both

Use module_language_name() from src.util.translations to render it. It takes the module language and the user's LanguageCode, accepts a raw integer or a stored name, and falls back to english with a warning for an unknown value, so a new id added in the database cannot take a view down.

src.util.enums.ModuleLanguage

Bases: IntEnum

Defines the languages a module can be taught in (as stored in Modulsprache)

A module can also be taught in both languages at once, which LanguageCode can not express, as the bot itself always speaks either english or german.

name property
name: str

The name of the Enum member (as lowercase).

from_language_code classmethod
from_language_code(language_code: str) -> Self

Creates a new enum based on the provided language code

Source code in src/util/enums.py
@classmethod
def from_language_code(cls, language_code: str) -> Self:
    """ Creates a new enum based on the provided language code"""
    return cls[language_code.upper()]
valid_languages classmethod
valid_languages() -> list[str]

Returns a list of all valid language codes

Source code in src/util/enums.py
@classmethod
def valid_languages(cls) -> list[str]:
    """ Returns a list of all valid language codes"""
    return [
        name.lower() for name in cls.__members__
    ]
values classmethod
values() -> list[int]

List of all valid enum values

Source code in src/util/enums.py
@classmethod
def values(cls) -> list[int]:
    """ List of all valid enum values"""
    return [
        member.value for member in cls
    ]

StudyCourse

Enumerates all supported university majors to ensure consistent string matching in the StartView.

src.util.enums.StudyCourse

Bases: IntEnum

Defines all valid courses of study

from_str classmethod
from_str(course_str: str) -> Self

Creates a new enum based on the provided course of study string

Source code in src/util/enums.py
@classmethod
def from_str(cls, course_str: str) -> Self:
    """ Creates a new enum based on the provided course of study string"""
    if course_str.upper() not in cls.__members__:
        raise ValueError(f"Invalid study course string: '{course_str}'")
    return cls[course_str.upper()]
values classmethod
values() -> list[int]

List of all valid enum values

Source code in src/util/enums.py
@classmethod
def values(cls) -> list[int]:
    """ List of all valid enum values"""
    return [
        member.value for member in cls
    ]

Nextcloud Tables API Integration

This module (src.util.tables) serves as the bridge between the bot and the faculty's Nextcloud instance. It handles authentication, data sanitization, and caching of lookup values.

sequenceDiagram
    participant Bot
    participant Cache
    participant API as Nextcloud API
    participant DB as Local DB
    Bot->>Cache: Check if data < 24h old
    alt Cache valid
        Cache-->>Bot: Return cached data
    else Cache expired
        Bot->>API: GET /tables/720/rows
        API-->>Bot: JSON response
        Bot->>Bot: Sanitize column names
        Bot->>Bot: Parse CP/SWS regex
        Bot->>Cache: Update cache (TTL=24h)
        Bot->>DB: Update modules table
        DB-->>Bot: Rows inserted
    end

Configuration

The connection is established via requests.auth.HTTPBasicAuth. Parameters are loaded from environment variables:

  • TABLES_URL: The base URL of the Nextcloud instance.
  • TABLES_USERNAME: The user/bot account name.
  • TABLES_PASSWORD: The app password or user password.

Timeout: Requests typically time out after 30 seconds.

Data Fetching (Low-Level)

These functions perform the actual HTTP requests to the Nextcloud Tables API endpoints.

Row Retrieval

The core function get_rows_from_view performs active data sanitization. It automatically fixes known typos in the faculty's column headers (e.g., Modultitel(englisch) → Modultitel (englisch)) to ensure consistent dictionary keys downstream.

src.util.tables.get_rows_from_view

get_rows_from_view(view_id: int) -> list[dict[str, Any]]

Retrieves the rows from a specific view and formats them.

Parameters:

Name Type Description Default
view_id int

The ID of the view to retrieve rows from.

required

Returns:

Type Description
list[dict[str, Any]]

A list of dictionaries representing the formatted rows.

Source code in src/util/tables.py
def get_rows_from_view(view_id: int) -> list[dict[str, Any]]:  # pyright: ignore[reportExplicitAny]
    """ Retrieves the rows from a specific view and formats them.

        Parameters:
            view_id: The ID of the view to retrieve rows from.

        Returns:
            A list of dictionaries representing the formatted rows.
    """
    cache_key = f"view_rows:{view_id}"
    cached = _get_from_cache(cache_key)
    if cached is not None:
        return [row.copy() for row in cached]

    columns = __get_columns_from_view(view_id)
    rows_data = __get_rows_from_view(view_id)

    result_rows = []
    for row in rows_data:  # pyright: ignore[reportAny]
        result: dict[str, Any] = {}  # pyright: ignore[reportExplicitAny]
        for cell in row:  # pyright: ignore[reportAny]
            title = (  # pyright: ignore[reportAny]
                columns[cell["columnId"]]  # pyright: ignore[reportAny]
                .replace("Modultitel ", "Modultitel")
                .replace("Modultitel(englisch)", "Modultitel (englisch)")
            )
            result[title] = cell["value"]
        result_rows.append(result)  # pyright: ignore[reportUnknownMemberType]
    if result_rows:
        _set_in_cache(cache_key, result_rows)
    return result_rows  # pyright: ignore[reportUnknownVariableType]

src.util.tables.get_rows

get_rows(table_id: int) -> list[TablesModuleDict]

Retrieves the rows from a specific table.

Parameters:

Name Type Description Default
table_id int

The ID of the table to retrieve rows from.

required

Returns:

Type Description
list[TablesModuleDict]

A list of dictionaries representing the rows of the table.

Source code in src/util/tables.py
def get_rows(table_id: int) -> list[TablesModuleDict]:
    """ Retrieves the rows from a specific table.

        Parameters:
            table_id: The ID of the table to retrieve rows from.

        Returns:
            A list of dictionaries representing the rows of the table.
    """
    cache_key = f"rows:{table_id}"
    cached = _get_from_cache(cache_key)
    if cached is not None:
        return [row.copy() for row in cached]  # pyright: ignore[reportReturnType]

    data: list[list[Any]] = get_json(  # pyright: ignore[reportExplicitAny, reportAny]
        f"{_API_BASE}/tables/{table_id}/rows/simple"
    )
    if not data:
        return []

    keys: list[str] = data[0]

    rows: list[TablesModuleDict] = [  # pyright: ignore[reportReturnType]
        dict(zip(keys, row)) for row in data[1:]
    ]
    if rows:
        _set_in_cache(cache_key, rows)
    return rows

Metadata & Schema

Functions to retrieve structure information, which is essential for dynamic UI generation.

src.util.tables.get_table_ids

get_table_ids() -> list[dict[str, Any]]

Retrieves the IDs and names of all tables.

Returns:

Type Description
list[dict[str, Any]]

A list of dictionaries containing table IDs, names, and ownership information.

Source code in src/util/tables.py
def get_table_ids() -> list[dict[str, Any]]:  # pyright: ignore[reportExplicitAny]
    """ Retrieves the IDs and names of all tables.

        Returns:
            A list of dictionaries containing table IDs, names, and ownership information.
    """
    data = get_json(f"{_API_BASE}/tables")  # pyright: ignore[reportAny]
    # pylint: disable=C0301 # (line-too-long)
    return [{"id": t["id"], "name": t["title"], "owner": t["ownership"]} for t in data]  # pyright: ignore[reportAny]

src.util.tables.get_scheme

get_scheme(table_id: int) -> Any

Retrieves the scheme for a specific table.

Parameters:

Name Type Description Default
table_id int

The ID of the table to retrieve scheme from.

required

Returns:

Type Description
Any

An object representing the json data.

Source code in src/util/tables.py
def get_scheme(table_id: int) -> Any:  # pyright: ignore[reportExplicitAny, reportAny]
    """ Retrieves the scheme for a specific table.

        Parameters:
            table_id: The ID of the table to retrieve scheme from.

        Returns:
            An object representing the json data.
    """
    cache_key = f"scheme:{table_id}"
    cached = _get_from_cache(cache_key)
    if cached is not None:
        return cached

    data = get_json(f"{_API_BASE}/tables/{table_id}/scheme")  # pyright: ignore[reportAny]
    result = data if data else []  # pyright: ignore[reportAny]
    if result:
        _set_in_cache(cache_key, result)
    return result

src.util.tables.get_defaults

get_defaults(view_id: int) -> dict[Any, Any]

Retrieves the default options for columns in a specific view.

Parameters:

Name Type Description Default
view_id int

The ID of the view to retrieve defaults from.

required

Returns:

Type Description
dict[Any, Any]

A dictionary mapping column titles to their default options.

Source code in src/util/tables.py
def get_defaults(view_id: int) -> dict[Any, Any]:  # pyright: ignore[reportExplicitAny]
    """ Retrieves the default options for columns in a specific view.

        Parameters:
            view_id: The ID of the view to retrieve defaults from.

        Returns:
            A dictionary mapping column titles to their default options.
    """
    cols = __get_columns_from_view(view_id)
    return {cols[key]: __get_defaults(key) for key in cols.keys()}  # pyright: ignore[reportAny]

Value Resolution & Caching (Smart Lookup)

The API often returns IDs (integers) for selection fields (like "Lehrstuhl" or "Sprache") instead of human-readable strings. The module implements a Caching-Mechanism to resolve these IDs efficiently.

  • Global Cache: Uses selectionse_dict to store mappings.
  • TTL (Time To Live): The cache is only refreshed if it is older than 24 hours (86400 seconds) to minimize API calls.

src.util.tables.get_value_by_id

get_value_by_id(id_: int, key: str) -> str

Retrieves the value of a specific key for a module identified by its ID.

Parameters:

Name Type Description Default
id_ int

The id value for the dictionary.

required
key str

The key whose dictionary is to be retrieved.

required

Returns:

Name Type Description
str

The value associated with the specified key for the module with the given ID.

or str

the id as a string, if the key (or id) is not found.

Source code in src/util/tables.py
def get_value_by_id(id_: int, key: str) -> str:
    """ Retrieves the value of a specific key for a module identified by its ID.

        Parameters:
            id_: The id value for the dictionary.
            key: The key whose dictionary is to be retrieved.

        Returns:
            The value associated with the specified key for the module with the given ID.
            or: the id as a string, if the key (or id) is not found.
    """
    __update_selections_dict()

    if selectionse_dict.keys().__contains__(key.lower()):
        if id_ in selectionse_dict[key.lower()].keys():
            return selectionse_dict[key.lower()][id_]
    return str(id_)

A class designed to hold a snapshot of module titles for the autocomplete features.

  • Search Algorithm: Uses rapidfuzz.process.extract for high-performance fuzzy matching.
  • Initialization: Loads the view (typically ID 2018) once during startup to reduce latency during user interaction.

src.util.tables.Catalogue

Catalogue(view: int)

Class for creation of a Module Catalog and searching for specific module names

Initializes the Catalogue with modules from a specific view.

Parameters:

Name Type Description Default
view int

The ID of the view to initialize the catalogue from.

required
Source code in src/util/tables.py
def __init__(self, view: int):
    """ Initializes the Catalogue with modules from a specific view.

        Parameters:
            view: The ID of the view to initialize the catalogue from.
    """
    self._view: int = view
    self._module_catalogue = []
    self._modules = []
    self._load()

find_match

find_match(module_name: str) -> list[ModuleMatch]

Finds matches for a given module name in the catalogue.

Parameters:

Name Type Description Default
module_name str

The name of the module to find matches for.

required

Returns:

Type Description
list[ModuleMatch]

A list of matches, each with the label to show and the id to open.

Source code in src/util/tables.py
def find_match(self, module_name: str) -> list[ModuleMatch]:
    """ Finds matches for a given module name in the catalogue.

        Parameters:
            module_name: The name of the module to find matches for.

        Returns:
            A list of matches, each with the label to show and the id to open.
    """
    if not self._modules:
        self._load()
    titles: list[str] = [entry.title for entry in self._modules]
    # Without `match_key` the comparison is case sensitive and umlaut sensitive.
    # `SOFTWARE ENGINEERING` found "Grundlagen der Theoretischen Informatik", and
    # `qualitaetsmanagement` put "Datenmanagement" above the module it names.
    matches = process.extract(
        module_name,
        titles,
        scorer=fuzz.WRatio,
        processor=match_key,
    )
    return [self._modules[match[2]] for match in matches]

get_module_list

get_module_list() -> list[str]

Returns a list of all module titles

Source code in src/util/tables.py
def get_module_list(self) -> list[str]:
    """ Returns a list of all module titles"""
    if not self._modules:
        self._load()
    return [entry.title for entry in self._modules]

Internationalization (i18n)

OSCAR is designed to be fully bilingual (German/English). The translation architecture relies on strict typing via Enums and a centralized dictionary storage for static UI elements.

Core Definition

The LanguageCode Enum is used throughout the codebase to ensure type safety when switching languages.

  • ISO 639 Compliance: Maps EN to 0 and DE to 1.
  • Attributes: Provides helpers like .values() and .from_language_code().

src.util.enums.LanguageCode

Bases: IntEnum

Defines the supported languages (as its language code)

See ISO 639 (Set 1)

from_language_code classmethod

from_language_code(language_code: str) -> Self

Creates a new enum based on the provided (ISO 639) language code

Source code in src/util/enums.py
@classmethod
def from_language_code(cls, language_code: str) -> Self:
    """ Creates a new enum based on the provided (ISO 639) language code"""
    return cls[language_code.upper()]

valid_languages classmethod

valid_languages() -> list[str]

Returns a list of all valid language codes

Source code in src/util/enums.py
@classmethod
def valid_languages(cls) -> list[str]:
    """ Returns a list of all valid language codes"""
    return [
        name.lower() for name in cls.__members__
    ]

Translation Dictionaries

Static UI texts are stored in nested dictionaries: dict[ContextKey, dict[LanguageCode, TranslatedString]].

General UI Texts

Used for global navigation, onboarding, and command feedback.

src.util.translations

This module contains various translation dictionarys for the OSCAR Discord bot.

LANGUAGES module-attribute

LANGUAGES: dict[LanguageCode, dict[LanguageCode, str]] = {LanguageCode.DE: {LanguageCode.DE: 'Deutsch', LanguageCode.EN: 'German'}, LanguageCode.EN: {LanguageCode.DE: 'Englisch', LanguageCode.EN: 'English'}}

START_TEXTS module-attribute

START_TEXTS: dict[str, dict[LanguageCode, str]] = {'welcome_title': {LanguageCode.DE: '# WILLKOMMEN BEI OSCAR', LanguageCode.EN: '# WELCOME TO OSCAR'}, 'welcome_text': {LanguageCode.DE: 'Willkommenstext hier', LanguageCode.EN: 'Welcome text here'}, 'before_info': {LanguageCode.DE: 'Bevor wir starten, brauchen wir ein paar Informationen über dich.', LanguageCode.EN: 'Before we start, we need some information about you.'}, 'language_question': {LanguageCode.DE: 'Bevorzugst du Englisch oder Deutsch als Sprache?', LanguageCode.EN: 'Do you prefer English or German as your language of choice?'}, 'semester_question': {LanguageCode.DE: '**Basierend auf deiner Serverrolle bist du im {semester}. Semester.\nStimmt das?**', LanguageCode.EN: '**Based on your server role, you are in the {semester} semester.\nIs this true?**'}, 'commands_info': {LanguageCode.DE: '**Hier sind die Befehle, die du nutzen kannst:**', LanguageCode.EN: '**Here are the commands you can use:**'}, 'thank_you': {LanguageCode.DE: 'Danke, dass du OSCAR benutzt.', LanguageCode.EN: 'Thank you for using OSCAR.'}, 'more_commands': {LanguageCode.DE: '\nEs gibt noch mehr, zum Beispiel `/klausuren`, `/fristen`, `/ansprechpartner` und `/standard_plan`. **`/help` erklärt jeden Befehl** und startet ihn auch, wenn du den Namen noch nicht kennst.', LanguageCode.EN: '\nThere are more, for example `/klausuren`, `/fristen`, `/ansprechpartner` and `/standard_plan`. **`/help` explains every command** and starts it too, if you do not know the name yet.'}, 'subject': {LanguageCode.DE: 'Welchen Studiengang studierst du?', LanguageCode.EN: 'Which subject are you studying?'}}

HELP_LANGUAGES module-attribute

HELP_LANGUAGES: dict[str, dict[LanguageCode, str]] = {'help': {LanguageCode.DE: 'Hilfe', LanguageCode.EN: 'Help'}, 'description': {LanguageCode.DE: 'Hey, willkommen bei OSCAR.\n\n**Öffnen** startet eine Funktion direkt.\n**Befehl** erklärt einen der Befehle im Slash-Menü.\n**Frage** beantwortet, was Studierende am häufigsten fragen.', LanguageCode.EN: 'Hey, welcome to OSCAR.\n\n**Open** starts a feature right away.\n**Command** explains one of the commands in the slash menu.\n**Question** answers what students ask most often.'}, 'select_option_1': {LanguageCode.DE: 'Modulinformationen', LanguageCode.EN: 'Module information'}, 'select_option_2': {LanguageCode.DE: 'Modulsuche', LanguageCode.EN: 'Module search'}, 'select_option_3': {LanguageCode.DE: 'Modulfilterung', LanguageCode.EN: 'Module filtering'}, 'select_option_4': {LanguageCode.DE: 'Semesterplanung', LanguageCode.EN: 'Semester Planning'}, 'select_option_5': {LanguageCode.DE: 'Sonstiges', LanguageCode.EN: 'Other'}, 'faq_placeholder': {LanguageCode.DE: 'Frage: Was Studierende oft fragen', LanguageCode.EN: 'Question: what students often ask'}, 'open_placeholder': {LanguageCode.DE: 'Öffnen: eine Funktion starten', LanguageCode.EN: 'Open: start a feature'}, 'command_placeholder': {LanguageCode.DE: 'Befehl: was ein Slash-Befehl tut', LanguageCode.EN: 'Command: what a slash command does'}, 'core_note': {LanguageCode.DE: 'Jeden Befehl kannst du auch tippen, das Slash-Menü führt sie alle. Die häufigsten Funktionen startest du oben unter **Öffnen**, ohne den Namen zu kennen.', LanguageCode.EN: 'You can type every command, the slash menu lists all of them. The most common features also start under **Open** above, no name needed.'}}

STUDY_COURSES module-attribute

STUDY_COURSES: dict[StudyCourse, dict[LanguageCode, str]] = {StudyCourse.BSC_INF: {LanguageCode.DE: 'B.Sc. Informatik', LanguageCode.EN: 'B.Sc. Computer Science'}, StudyCourse.BSC_CV: {LanguageCode.DE: 'B.Sc. Computervisualistik', LanguageCode.EN: 'B.Sc. Computervisualistik'}, StudyCourse.BSC_INGINF: {LanguageCode.DE: 'B.Sc. Ingenieurinformatik', LanguageCode.EN: 'B.Sc. Ingenieurinformatik'}, StudyCourse.BSC_WIF: {LanguageCode.DE: 'B.Sc. Wirtschaftsinformatik', LanguageCode.EN: 'B.Sc. Wirtschaftsinformatik'}, StudyCourse.BSC_INF_BILINGUAL: {LanguageCode.DE: 'B.Sc. Informatik (bilingual)', LanguageCode.EN: 'B.Sc. Computer Science (bilingual)'}, StudyCourse.MSC_INF: {LanguageCode.DE: 'M.Sc. Informatik', LanguageCode.EN: 'M.Sc. Computer Science'}, StudyCourse.MSC_INGINF: {LanguageCode.DE: 'M.Sc. Ingenieurinformatik', LanguageCode.EN: 'M.Sc. Ingenieurinformatik'}, StudyCourse.MSC_WIF: {LanguageCode.DE: 'M.Sc. Wirtschaftsinformatik', LanguageCode.EN: 'M.Sc. Wirtschaftsinformatik'}, StudyCourse.MSC_DKE: {LanguageCode.DE: 'M.Sc. Data and Knowledge Engineering', LanguageCode.EN: 'M.Sc. Data and Knowledge Engineering'}, StudyCourse.MSC_DE: {LanguageCode.DE: 'M.Sc. Digital Engineering', LanguageCode.EN: 'M.Sc. Digital Engineering'}, StudyCourse.MSC_VC: {LanguageCode.DE: 'M.Sc. Visual Computing', LanguageCode.EN: 'M.Sc. Visual Computing'}}

Feature Specific Texts

Texts used in specific views like Module Details (AllInfoView) or Feedback Forms.

src.util.translations

This module contains various translation dictionarys for the OSCAR Discord bot.

INFO_TEXTS module-attribute

INFO_TEXTS: dict[str, dict[LanguageCode, str]] = {'responsibility': {LanguageCode.DE: 'Verantwortung', LanguageCode.EN: 'Responsibility'}, 'lecturer': {LanguageCode.DE: 'Dozent/Dozentin', LanguageCode.EN: 'Lecturer'}, 'course': {LanguageCode.DE: 'Lehrveranstaltung', LanguageCode.EN: 'Course'}, 'abbreviation': {LanguageCode.DE: 'Kürzel', LanguageCode.EN: 'Abbreviation'}, 'semester': {LanguageCode.DE: 'Fachsemester', LanguageCode.EN: 'Semester'}, 'duration': {LanguageCode.DE: 'Dauer', LanguageCode.EN: 'Duration'}, 'language': {LanguageCode.DE: 'Sprache', LanguageCode.EN: 'Language'}, 'level': {LanguageCode.DE: 'Niveau', LanguageCode.EN: 'Level'}, 'content': {LanguageCode.DE: 'Inhalt', LanguageCode.EN: 'Content'}, 'ilo': {LanguageCode.DE: 'Angestrebte Lernergebnisse', LanguageCode.EN: 'Intended learning outcomes'}, 'effort': {LanguageCode.DE: 'Arbeitsaufwand', LanguageCode.EN: 'Effort'}, 'module_number': {LanguageCode.DE: 'Modulnummer', LanguageCode.EN: 'Module number'}}

MODULE_TEXTS module-attribute

MODULE_TEXTS: dict[str, dict[LanguageCode, str]] = {'added_to_plan': {LanguageCode.DE: '{title} wurde zu deiner Semesterübersicht hinzugefügt.', LanguageCode.EN: '{title} was added to your semester overview.'}, 'plan_failed': {LanguageCode.DE: 'Ich konnte {title} gerade nicht speichern. Versuch es bitte später nochmal.', LanguageCode.EN: 'I could not save {title} right now. Please try again later.'}, 'not_found': {LanguageCode.DE: 'Ich habe kein Modul mit dem Namen **{name}** gefunden. Wähle bitte einen Vorschlag aus der Liste.', LanguageCode.EN: 'I found no module called **{name}**. Please pick a suggestion from the list.'}, 'kuerzel': {LanguageCode.DE: 'Kürzel: ', LanguageCode.EN: 'Abbreviation: '}, 'sprache': {LanguageCode.DE: 'Sprache: ', LanguageCode.EN: 'Language: '}, 'dozent': {LanguageCode.DE: 'Dozent: ', LanguageCode.EN: 'Lecturer: '}}

FEEDBACK_TEXTS module-attribute

FEEDBACK_TEXTS: dict[str, dict[LanguageCode, str]] = {'welcome_text': {LanguageCode.DE: 'Vielen Dank, dass du dich dazu entschlossen hast uns Feedback zu geben.\nIm Folgenden findest du einige Fragen, bitte wähle das zutreffendste aus.\nIn den Textfeldern kanst du uns mitteilen, was dir noch auf dem Herze liegt.\nDamit hilfst du uns sehr weiter :)', LanguageCode.EN: "Thanks, that you have decided to give us feedback.\nIn the following you'll find some questions, please select the most fitting one.\nYou can tell us what your further toughts are in the textfiels below.\nThis will help is immense :)"}, 'choose_1': {LanguageCode.DE: 'trifft voll zu', LanguageCode.EN: 'totally applies'}, 'choose_2': {LanguageCode.DE: 'trifft eher zu', LanguageCode.EN: 'more likely to apply'}, 'choose_3': {LanguageCode.DE: 'trifft eher nicht zu', LanguageCode.EN: "doesn't really apply"}, 'choose_4': {LanguageCode.DE: 'trifft nicht zu', LanguageCode.EN: "doesn't apply"}, 'improve': {LanguageCode.DE: 'Was kann verbessert werden?', LanguageCode.EN: 'What could be improved?'}, 'wish': {LanguageCode.DE: 'Was wünschst du dir noch?', LanguageCode.EN: 'What is something you wish for?'}, 'bug': {LanguageCode.DE: 'Hast du einen Bug gefunden?', LanguageCode.EN: 'Did you find a bug?'}}

FEEDBACK_REVIEW module-attribute

FEEDBACK_REVIEW: dict[str, dict[LanguageCode, str]] = {'review_feedback': {LanguageCode.DE: 'Feedback überprüfen', LanguageCode.EN: 'Review Feedback'}, 'zoom': {LanguageCode.DE: 'Zoom:', LanguageCode.EN: 'Zoom:'}, 'intuitiveness': {LanguageCode.DE: 'Intuitivität', LanguageCode.EN: 'Intuitiveness'}, 'discoverability': {LanguageCode.DE: 'Auffindbarkeit', LanguageCode.EN: 'Discoverability'}, 'usefulness': {LanguageCode.DE: 'Nützlichkeit', LanguageCode.EN: 'Usefulness'}, 'houre_suffix': {LanguageCode.DE: 'h', LanguageCode.EN: 'h'}, 'day_suffix': {LanguageCode.DE: 'T', LanguageCode.EN: 'd'}, 'week_suffix': {LanguageCode.DE: 'W', LanguageCode.EN: 'w'}, 'month_suffix': {LanguageCode.DE: 'M', LanguageCode.EN: 'mo'}, 'year_suffix': {LanguageCode.DE: 'J', LanguageCode.EN: 'y'}, 'all_time': {LanguageCode.DE: 'Max', LanguageCode.EN: 'max'}, 'rating': {LanguageCode.DE: 'Bewertung', LanguageCode.EN: 'Rating'}, 'metrics_overview': {LanguageCode.DE: 'Metriken Übersicht', LanguageCode.EN: 'Metrics Overview'}, 'responses': {LanguageCode.DE: 'Antworten', LanguageCode.EN: 'responses'}, 'timeline': {LanguageCode.DE: 'Zeitachse', LanguageCode.EN: 'Timeline'}, 'data_download': {LanguageCode.DE: 'Daten herunterladen', LanguageCode.EN: 'Download Data'}, 'finished_download': {LanguageCode.DE: 'Dein Feedback-Daten-Download ist fertig!', LanguageCode.EN: 'Your feedback data download is ready!'}}

Helper Functions

The t Function

The standard retrieval function used in Views. It safely looks up a key in a dictionary for a given language code.

src.util.translations.t

t(lang: LanguageCode, key: str, translations: dict[str, dict[LanguageCode, str]]) -> str

Gibt die Übersetzung für einen Key in der angegebenen Sprache zurück.

Source code in src/util/translations.py
def t(lang: LanguageCode, key: str, translations: dict[str, dict[LanguageCode, str]]) -> str:
    """Gibt die Übersetzung für einen Key in der angegebenen Sprache zurück."""
    return translations.get(key, {}).get(lang, f"[{key}]")

Formatters

Specific helpers to format data types localized.

src.util.translations.format_duration

format_duration(seconds: int | None, language_code: LanguageCode = LanguageCode.EN) -> str

Format duration in seconds to human-readable string.

Source code in src/util/translations.py
def format_duration(seconds: int|None, language_code: LanguageCode = LanguageCode.EN) -> str:
    """Format duration in seconds to human-readable string."""
    if seconds is None:
        return FEEDBACK_REVIEW["all_time"][language_code]
    if seconds < 86400:
        hours = seconds // 3600
        return f"{hours}{FEEDBACK_REVIEW['houre_suffix'][language_code]}"
    if seconds < 604800:
        days = seconds // 86400
        return f"{days}{FEEDBACK_REVIEW['day_suffix'][language_code]}"
    if seconds < 2629743:
        weeks = seconds // 604800
        return f"{weeks}{FEEDBACK_REVIEW['week_suffix'][language_code]}"
    if seconds < 31556926:
        months = seconds // 2629743
        return f"{months}{FEEDBACK_REVIEW['month_suffix'][language_code]}"
    years = seconds // 31556926
    return f"{years}{FEEDBACK_REVIEW['year_suffix'][language_code]}"

Dynamic Content Translation

Unlike static UI texts, module data (descriptions, learning goals) is stored in the database. The Module class implements a hybrid approach:

  1. Database Lookup: Checks if a manual English translation exists (title_en).
  2. Fallback (Auto-Translation): If no translation is found, it uses the translate library to generate one on the fly.

See: src.util.module.Module._get_translation for implementation details.