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)
Mehr aus der Kategorie „Falkencode“
- Falkencode: app.py — Webserver & WebSocket 09.09.2026
- Falkencode: engine.py — Die Spiellogik 09.09.2026
- Falkencode: db.py — Konten, Spielstand, Post 09.09.2026
- Falkencode: gen_world.py — Der Weltgenerator 09.09.2026