Falkencode · 09.09.2026

Falkencode: uebersetz.py — Der DE/EN-Katalog

uebersetz.py macht Falkenmoor zweisprachig — ohne die Engine anzufassen. Der Trick: ein zeilenbasierter Katalog aus regulären Ausdrücken mit Platzhaltern. Eine Zeile wie „Du kaufst Kruger Bier für 1 Gold. (9 G übrig)“ wird per Muster erkannt und zu „You buy Kruger Bier for 1 gold. (9 G left)“ — dynamische Werte bleiben erhalten.

Wichtig ist der Fallback: Für keine Regel passende Zeilen bleiben einfach deutsch — nichts bricht, und der Katalog kann beliebig wachsen. Auch die englische Hilfe, der Willkommensgruß und die Richtungsübersetzung (norden → north) leben hier.

#!/usr/bin/env python3
"""Zweisprachiger Ausgabekatalog (de/en) für Falkenmoor — Stufe 2.
Zeilen, für die keine Regel passt, bleiben auf Deutsch (Fallback).

Umfang: Gerüstzeilen der Raumansicht, Bewegung, Kampf, Handel, Kneipe,
Bank, Chat, Quest-Kopfzeilen, Status, Hilfe. Weltinhalte (Raum-, NPC- und
Itemnamen bzw. -texte) sind Stufe 3 und erscheinen weiter deutsch."""
import re

RICHTUNG_EN = {
    "norden": "north", "sueden": "south", "osten": "east", "westen": "west",
    "nordosten": "northeast", "nordwesten": "northwest",
    "suedosten": "southeast", "suedwesten": "southwest",
    "oben": "up", "unten": "down", "runter": "down", "hoch": "up",
}
PHASE_EN = {"nacht": "night", "morgengrauen": "dawn", "tag": "day",
            "abenddaemmerung": "dusk"}
ATMO_EN = {
    "Die Nacht liegt dunkel über dem Land.": "Night lies dark over the land.",
    "Am Horizont graut der Morgen.": "Dawn is breaking on the horizon.",
    "Die Sonne steht hoch am Himmel.": "The sun is high in the sky.",
    "Der Himmel glüht in Abendrot.": "The sky glows in evening red.",
}

# Zeilenregeln: (Muster, Ersetzung oder Funktion für re.sub)
_ZEILEN = [
    # --- Raumansicht ---
    (r"^\((Tag|Nacht|Morgengrauen|Abenddaemmerung), (\d+) Uhr\) — (.+)$",
     lambda m: f"({PHASE_EN[m.group(1).lower()]}, {m.group(2)} o'clock) — "
               f"{ATMO_EN.get(m.group(3), m.group(3))}"),
    (r"^Hier sind: (.*)$", r"Present: \1"),
    (r"^Andere Helden: (.*)$", r"Other heroes: \1"),
    (r"^Am Boden liegen: (.*)$", r"On the ground: \1"),
    (r"^Ausgänge: (.*)$", lambda m: "Exits: " + _exits_en(m.group(1))),
    # --- Bewegung ---
    (r"^(.+) geht Richtung (\w+)\.$",
     lambda m: f"{m.group(1)} walks {_dir_en(m.group(2))}."),
    (r"^Dahin geht es nicht\. Ausgänge: (.*)$",
     lambda m: "You can't go there. Exits: " + _exits_en(m.group(1))),
    (r"^Hier gibt es keine Ausgänge\.$", "There are no exits here."),
    (r"^Du bist im Kampf! Erst FLIEHE oder siege\.$",
     "You are in combat! FLEE first, or finish the fight."),
    (r"^Dorthin führt kein Weg\.$", "No path leads there."),
    (r"^⚔ (.+) fällt über dich her!$", r"⚔ \1 attacks you!"),
    # --- Kampf ---
    (r"^⚔ Du triffst (.+) für (\d+) Schaden\. \((\d+)/(\d+) HP\)$",
     r"⚔ You hit \1 for \2 damage. (\3/\4 HP)"),
    (r"^💢 (.+) trifft dich für (\d+) Schaden\. \(Du: (\d+)/(\d+) HP\)$",
     r"💢 \1 hits you for \2 damage. (You: \3/\4 HP)"),
    (r"^☠ (.+) ist besiegt! \(\+(\d+) XP\)$", r"☠ \1 is defeated! (+\2 XP)"),
    (r"^Beute: (.*)$", r"Loot: \1"),
    (r"^✨ (.+) trifft (.+) für (\d+) Schaden\.",
     r"✨ \1 hits \2 for \3 damage."),
    (r"^✨ Heilung: (\d+)/(\d+) HP\.$", r"✨ Healing: \1/\2 HP."),
    (r"^✨ Der Heimwind trägt dich zum Marktplatz\.$",
     "✨ The homewind carries you to the market square."),
    (r"^✦ Du wirst zum Startraum versetzt\.$",
     "✦ You are teleported to the market square."),
    (r"^Du bist bereits am Start\.$", "You are already at the start."),
    (r"^💀 Du bist gefallen\.\.\. Der Dorfbader weckt dich auf dem Marktplatz\. "
     r"Du verlierst (\d+) Gold \(Bargeld\)\.(?: Dein Bankguthaben \((\d+) Gold\) ist sicher\.)?$",
     lambda m: ("💀 You fell... The healer wakes you on the market square. "
                f"You lose {m.group(1)} gold (cash)."
                + (f" Your bank balance ({m.group(2)} gold) is safe."
                   if m.group(2) else ""))),
    # --- Gegenstände ---
    (r"^So etwas liegt hier nicht\.$", "Nothing like that lies here."),
    (r"^Das hast du nicht\.$", "You don't have that."),
    (r"^Was denn nehmen\? NIMM <Gegenstand> · NIMM ALLES$",
     "Take what? TAKE <item> · TAKE ALL"),
    (r"^Dein Beutel ist voll \((\d+)/(\d+)\)\. VERKAUF, ABLEGEN oder die Truhe im Haus schafft Platz\.$",
     lambda m: f"Your bag is full ({m.group(1)}/{m.group(2)}). "
               "SELL, DROP or the chest in your house makes room."),
    (r"^Du nimmst alles: (.+)\. \((\d+) (?:Gegenstände|Gegenstand)\)"
     r"( — Dein Beutel ist voll \((\d+)/(\d+)\), (\d+) blieben liegen\.)?$",
     lambda m: (f"You take everything: {m.group(1)}. "
                f"({m.group(2)} item{'' if m.group(2) == '1' else 's'})"
                + (f" — Your bag is full ({m.group(4)}/{m.group(5)}), "
                   f"{m.group(6)} stayed on the ground." if m.group(3) else ""))),
    (r"^Du nimmst (.+)\.$", r"You take \1."),
    (r"^Du legst (.+) ab\.$", r"You put down \1."),
    (r"^In deinem Beutel \((\d+)/(\d+)\):$", r"In your bag (\1/\2):"),
    (r"^  \(leer\)$", "  (empty)"),
    # --- Truhe (Haus) ---
    (r"^Die Truhe ist in deinem Haus am Marktplatz \(Ausgang: haus\)\.$",
     "The chest is in your house at the market square (exit: haus)."),
    (r"^🧰 Deine Truhe \((\d+)/100 Fächer\):$",
     lambda m: f"🧰 Your chest ({m.group(1)}/100 slots):"),
    (r"^  \(leer\) — LEGE <x> lagert etwas ein\.$",
     "  (empty) — STORE <x> stores an item."),
    (r"^Herausnehmen: HOL <x> · Einlagern: LEGE <x>$",
     "Fetch: FETCH <x> · Store: STORE <x>"),
    (r"^Was einlagern\? LEGE <Gegenstand>$", "Store what? STORE <item>"),
    (r"^Was herausnehmen\? HOL <Gegenstand>$", "Fetch what? FETCH <item>"),
    (r"^Ausgerüstete Gegenstände erst ABLEGEN, dann einlagern\.$",
     "Unequip equipped items first (DROP), then store them."),
    (r"^Deine Truhe ist voll \(100/100 Fächer\)\.$", "Your chest is full (100/100 slots)."),
    (r"^Du legst (.+) in die Truhe\. \((\d+)/100 Fächer belegt\)$",
     r"You put \1 in the chest. (\2/100 slots used)"),
    (r"^Du holst (.+) aus der Truhe\. \((\d+)/100 Fächer belegt\)$",
     r"You take \1 from the chest. (\2/100 slots used)"),
    (r"^Das liegt nicht in deiner Truhe\. \(TRUHE zeigt den Inhalt\)$",
     "That's not in your chest. (CHEST shows the contents)"),
    (r"^Du führst jetzt (.+) \(Schaden \+(\d+)\)\.$",
     r"You now wield \1 (damage +\2)."),
    (r"^Du trägst jetzt (.+) \(Verteidigung \+(\d+)\)\.$",
     r"You now wear \1 (defense +\2)."),
    # --- Kneipe/Handel ---
    (r"^Du trinkst (.+)\. \+(\d+) HP \((\d+)/(\d+) HP, (\d+)/(\d+) Mana\)$",
     r"You drink \1. +\2 HP (\3/\4 HP, \5/\6 mana)"),
    (r"^Du trinkst (.+)\. \+(\d+) Mana \((\d+)/(\d+) HP, (\d+)/(\d+) Mana\)$",
     r"You drink \1. +\2 mana (\3/\4 HP, \5/\6 mana)"),
    (r"^Du isst (.+) und fühlst dich gestärkt\. \(\+(\d+) HP, (\d+)/(\d+) HP\)$",
     r"You eat \1 and feel strengthened. (+\2 HP, \3/\4 HP)"),
    (r"^Du isst (.+) und fühlst dich gestärkt\. \((\d+)/(\d+) HP\)$",
     r"You eat \1 and feel strengthened. (\2/\3 HP)"),
    (r"^Du kaufst (.+) für (\d+) Gold\. \((\d+) G übrig\)$",
     r"You buy \1 for \2 gold. (\3 G left)"),
    (r"^Du verkaufst (.+) für (\d+) Gold\. \((\d+) G\)$",
     r"You sell \1 for \2 gold. (\3 G)"),
    (r"^Hier gibt niemandem etwas zu kaufen\.$",
     "Nobody here sells anything."),
    (r"^Hier kauft niemand etwas\.$", "Nobody here buys anything."),
    (r"^Questgegenstände kannst du nicht verkaufen\.$",
     "You can't sell quest items."),
    # --- Bank ---
    (r"^🏦 Du zahlst (\d+) Gold in die Stadtkasse ein\. "
     r"\(Bargeld: (\d+) G · Guthaben: (\d+) G\)$",
     r"🏦 You deposit \1 gold. (Cash: \2 G · Balance: \3 G)"),
    (r"^🏦 Du hebst (\d+) Gold ab\. \(Bargeld: (\d+) G · Guthaben: (\d+) G\)$",
     r"🏦 You withdraw \1 gold. (Cash: \2 G · Balance: \3 G)"),
    (r"^🏦 Bankguthaben: (\d+) Gold · Bargeld: (\d+) Gold\.(.*)$",
     lambda m: f"🏦 Bank balance: {m.group(1)} gold · Cash: {m.group(2)} gold."
               f" Deposit and withdraw at the town hall."
               if "Stadtkasse" in m.group(3) else
               f"🏦 Bank balance: {m.group(1)} gold · Cash: {m.group(2)} gold."),
    # --- Quest ---
    (r"^★ NEUE QUEST: (.+) — (.+)$", r"★ NEW QUEST: \1 — \2"),
    (r"^✔ QUEST ABGESCHLOSSEN: (.+)! \((.+) dankt dir\.\)$",
     r"✔ QUEST COMPLETED: \1! (\2 thanks you.)"),
    (r"^  Belohnung: (.+)$", r"  Reward: \1"),
    (r"^\(\+?(\d+) XP\)$", r"(\1 XP)"),
    # --- Chat ---
    (r"^Du sagst: „(.*)“$", r'You say: "\1"'),
    (r"^Du rufst: „(.*)“$", r'You shout: "\1"'),
    # --- Untersuche/Status ---
    (r"^Zustand: (.+)\.$", r"Condition: \1."),
    (r"^Einschätzung: (.+)$", lambda m: "Assessment: " + _EINSCHAETZUNG.get(
        m.group(1), m.group(1))),
    # --- System ---
    (r"^Was meinst du mit '(.+)'\? Tippe HILFE für alle Befehle\.$",
     r"What do you mean by '\1'? Type HELP for all commands."),
]

_EINSCHAETZUNG = {
    "Ein Kinderspiel.": "A piece of cake.",
    "Wirkt schwächer als du.": "Weaker than you.",
    "Etwa auf Augenhöhe — ein fairer Kampf.": "Even odds — a fair fight.",
    "Wirkt gefährlich — besser gewappnet als du.": "Dangerous — better armed than you.",
    "Lebensgefährlich! Ohne Vorbereitung wird das nichts.":
        "Lethal! Without preparation, this won't end well.",
}

_HILFE_EN = (
    "═══ FALKENMOOR — COMMANDS ═══\n"
    "Movement:   NORTH/SOUTH/EAST/WEST (n/s/o... also 'w' for west) · LOOK\n"
    "Things:     EXAMINE <x> · 'look <x>' — details (also: x <x>)\n"
    "Items:      TAKE <x> · TAKE ALL · DROP <x> · USE/DRINK <x> · WEAR <x> · INVENTORY\n"
    "House:      CHEST · STORE <x> · FETCH <x> — your house at the market (exit: haus)\n"
    "Combat:     ATTACK <name> · FLEE\n"
    "Talk:       TALK <name> · GIVE <item> <npc>\n"
    "Trade:      BUY <ware> · SELL <item> (at shops)\n"
    "Bank:       DEPOSIT <x> · WITHDRAW <x> · BALANCE — town hall\n"
    "Chat:       SAY <text> (room) · SHOUT <text> (all) · PLAYERS (who's online)\n"
    "Mail:       MAIL (inbox) · MAIL <name> <text> — mailbox at the market\n"
    "Board:      BOARD (read) · BOARD <text> (note) — market square\n"
    "Magic:      SPELL (list) · SPELL <name> [foe] — costs mana\n"
    "Guilds:     GUILD · GUILD JOIN <name> (town hall) · GUILDCALL <text>\n"
    "Quests:     QUESTS · STATUS · HELP · QUIT (log out)\n"
    "Race:       RACE · RACE <zwerg|elf|ork|mensch> — choose once\n"
    "Language:   LANGUAGE EN / LANGUAGE DE\n"
    "Tip: Shops close at night — only the herbal witch and the taverns serve.\n"
    "Locked paths need the right key! (Room/NPC/item names remain German\n"
    "for now — world translation is work in progress.)"
)


def _dir_en(wort):
    return RICHTUNG_EN.get(wort.lower(), wort)


def _exits_en(liste):
    """Ausgänge-Liste: Richtungs- und Statuswörter übersetzen."""
    teile = []
    for t in liste.split(", "):
        t2 = t.strip()
        if t2.endswith("(verschlossen)"):
            teile.append(_dir_en(t2[:-14].strip()) + " (locked)")
        else:
            teile.append(_dir_en(t2))
    return ", ".join(teile)


def zeile(zeile, sprache):
    """Eine Zeile übersetzen (nur 'en' ändert etwas). Erste passende Regel gewinnt."""
    if sprache != "en":
        return zeile
    for muster, ersatz in _ZEILEN:
        neu = re.sub(muster, ersatz, zeile)
        if neu != zeile:
            return neu
    return zeile


def text(text, sprache):
    """Mehrzeiligen Text zeilenweise übersetzen."""
    if sprache != "en":
        return text
    return "\n".join(zeile(z, sprache) for z in text.split("\n"))


def hilfe(sprache):
    return _HILFE_EN if sprache == "en" else None


def willkommen(name, titel, post_anzahl, sprache):
    """Begrüßungsnachricht beim Login."""
    if sprache != "en":
        return None  # None =Caller nutzt die deutsche Standardfassung
    hinweis = ""
    if post_anzahl:
        hinweis = (f"\n📬 You have {post_anzahl} new letter"
                   f"{'s' if post_anzahl != 1 else ''} at the mailbox on the market square!")
    return (f"Welcome back to {titel}, {name}!\n"
            "Your progress is saved automatically. Type HELP for all commands."
            + hinweis)

← Zurück zum Lehrpfad