diff --git a/config.json.example b/config.json.example index 9c7767e..c5f9041 100644 --- a/config.json.example +++ b/config.json.example @@ -3,6 +3,7 @@ "token": "", "server": , "vc": , + "lang": "de" "formatting": "┇", "emojis": true }, diff --git a/requirements.txt b/requirements.txt index d49c13c..56144ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ numpy==2.5.1 pandas==3.0.3 propcache==0.5.2 py-cord==2.8.0 +pyyaml==6.0.3 pycparser==3.0 pynacl==1.6.2 python-dateutil==2.9.0.post0 @@ -20,4 +21,4 @@ requests==2.34.2 six==1.17.0 typing-extensions==4.16.0 urllib3==2.7.0 -yarl==1.24.2 +yarl==1.24.2 \ No newline at end of file diff --git a/src/api/transitous.py b/src/api/transitous.py index fc02f51..06cc738 100644 --- a/src/api/transitous.py +++ b/src/api/transitous.py @@ -5,9 +5,12 @@ from datetime import datetime, timezone from src.utils import logger, get_train_name, validate_connection, parse_iso from src.config import config +from src.lang.locales import lang + stations = config.connections.stations blacklist = config.connections.blacklist +long_name_lang = lang.channel.long_name user_agent = config.http.user_agent headers = { @@ -220,9 +223,9 @@ def get_trip_details(random_connection: dict | None) -> dict | None: train_name = get_train_name(display_name, mode) if train_from == from_station: - long_name = f"{train_name} nach {goes_to} von {train_from}" + long_name = long_name_lang.train_from() else: - long_name = f"{train_name} nach {goes_to} über {from_station}" + long_name = long_name_lang.train_via() trip_details = { "long_name": long_name, diff --git a/src/config.py b/src/config.py index 33957b9..b6071e1 100644 --- a/src/config.py +++ b/src/config.py @@ -1,5 +1,4 @@ import json -import os from dataclasses import dataclass, field from typing import Optional @@ -8,6 +7,7 @@ class DiscordConfig: token: str server: int vc: int + lang: str formatting: str emojis: bool diff --git a/src/data/locales/de.yaml b/src/data/locales/de.yaml new file mode 100644 index 0000000..4fe234c --- /dev/null +++ b/src/data/locales/de.yaml @@ -0,0 +1,17 @@ +channel: + status: "Ankunft um {arrival}" + long_name: + train_from: "{train_name} nach {goes_to} von {train_from}" + train_via: "{train_name} nach {goes_to} über {from_station}" +embeds: + footer: + notice: "Daten großzügig bereitgestellt von https://transitous.org" + info: + description: "Abfahrt von {station} um {departure}. Ankunft um {arrival}" + via: "Über" + "via_and": "und" + "next_stop": "Nächster Halt" + announcement: + title: "Informationen zu ihrer Fahrt" + "end_of_connection": + message: "Sehr geehrte Fahrgäste,\nIn wenigen Minuten erreichen wir {destination}. Unsere Reise endet dort\n\nWir wünschen Ihnen eine angenehme Weiterreise.\n\nVielen Dank für ihr Vertrauen und auf Wiedersehen." diff --git a/src/data/locales/en.yaml b/src/data/locales/en.yaml new file mode 100644 index 0000000..4373207 --- /dev/null +++ b/src/data/locales/en.yaml @@ -0,0 +1,17 @@ +channel: + status: "Ankunft um {arrival}" + long_name: + train_from: "{train_name} to {goes_to} from {train_from}" + train_via: "{train_name} to {goes_to} via {from_station}" +embeds: + footer: + notice: "Data provided by https://transitous.org" + info: + description: "Departure from {station} at {departure}. Arrival by {arrival}" + via: "via" + "via_and": "and" + "next_stop": "Next stop" + announcement: + title: "Information about your trip" + "end_of_connection": + message: "Dear Passengers,\nwe will be arriving at {destination} in a few minutes. Our journey ends there.\n\nWe wish you a pleasant onward journey.\n\nThank you for your patronage, and goodbye." diff --git a/src/dc/embeds.py b/src/dc/embeds.py index f99430f..c203343 100644 --- a/src/dc/embeds.py +++ b/src/dc/embeds.py @@ -3,9 +3,12 @@ import random from src.utils import get_operator_metadata, get_next_station, format_via_list, format_stop_list from src.dc.helpers import format_timestamp_to_dc +from src.lang.locales import lang + +lang_embed = lang.embeds def build_embed_footer(mode: str, slogans): - footer_notice = f"Data provided by https://transitous.org • mode: {mode}" + footer_notice = f"{lang.embeds.footer.notice()} • mode: {mode}" icon = "https://raw.githubusercontent.com/kaaninchen/Gleiswechsel/refs/heads/main/src/data/assets/transitous-logo.png" if slogans is not None: @@ -22,14 +25,15 @@ def build_embed_footer(mode: str, slogans): def build_info_embed() -> discord.Embed: from src.dc.handlers import trip + station = trip["station"] agency = trip["agency"] metadata = get_operator_metadata(agency, trip["route_color"], trip["mode"]) departure = format_timestamp_to_dc(trip["departure"]) arrival = format_timestamp_to_dc(trip["arrival"]) - + embed = discord.Embed( title = trip["long_name"], - description=f"Abfahrt von {trip["station"]} um {departure}. Ankunft um {arrival}", + description=lang_embed.info.description(), color = metadata["color"] ) @@ -40,12 +44,12 @@ def build_info_embed() -> discord.Embed: if next_stop: next_stop_station = next_stop.get("name") - via = format_via_list(stops) + via = format_via_list(stops, lang_embed.info.via_and()) if via: - embed.add_field(name="Über", value=via, inline=False) + embed.add_field(name=lang_embed.info.via(), value=via, inline=False) if next_stop: - embed.add_field(name="Nächster Halt", value=f"__{next_stop_station}__", inline=False) + embed.add_field(name=lang_embed.info.next_stop(), value=f"__{next_stop_station}__", inline=False) route_fields = format_stop_list(stops, next_stop_station) for field_name, field_value in route_fields: @@ -65,7 +69,7 @@ def build_announcement_embed(msg): metadata = get_operator_metadata(agency, trip["route_color"], trip["mode"]) embed = discord.Embed( - title = "Informationen zu ihrer Fahrt", + title = lang_embed.announcement.title(), description=msg, color=metadata["color"] ) diff --git a/src/dc/handlers.py b/src/dc/handlers.py index 93eddeb..8a5bbb0 100644 --- a/src/dc/handlers.py +++ b/src/dc/handlers.py @@ -1,12 +1,14 @@ import discord import asyncio import random -from datetime import datetime, timedelta, date +from datetime import datetime, timedelta from src.utils import logger, channel_formatting, choose_connection, get_sound_path, LOCAL_TZ, get_next_station from src.config import config +from src.lang.locales import lang _scheduled_task: asyncio.Task | None = None +channel_lang = lang.channel async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = False): global trip, _scheduled_task @@ -35,7 +37,7 @@ async def rename_vc(bot: discord.Bot, voice_channel, from_scheduler: bool = Fals formatting = channel_formatting(mode) await voice_channel.edit(name=f"{formatting}{long_name}") - await voice_channel.set_status(f"Ankunft um {arrival}") + await voice_channel.set_status(channel_lang.status()) logger(f"Updated channel name!") @@ -56,8 +58,7 @@ async def announcer(announcement: str, voice_channel: discord.VoiceChannel, dest announcement_status = await voice_announcer(destination, voice_channel) if announcement_status: return - embed = build_announcement_embed( - f'Sehr geehrte Fahrgäste,\nIn wenigen Minuten erreichen wir {destination}. Dieser Zug endet dort.\n\nWir wünschen Ihnen eine angenehme Weiterreise.\n\nVielen Dank für ihr Vertrauen und auf Wiedersehen.') + embed = build_announcement_embed(lang.embeds.announcement.end_of_connection.message()) case "transfer": embed = build_info_embed() case _: @@ -96,7 +97,7 @@ async def _schedule_next_transfer(bot: discord.Bot, arrival_dt: datetime, voice_ if wait_seconds > 0: remaining = str(timedelta(seconds=wait_seconds)) - logger(f"Nexxt transfer in {remaining.split('.')[0]} ({arrival_dt.strftime('%H:%M')} Uhr)") + logger(f"Next transfer in {remaining.split('.')[0]} ({arrival_dt.strftime('%H:%M')} Uhr)") if wait_seconds > announcement_countdown: wait_until_end_announcement = wait_seconds - announcement_countdown diff --git a/src/lang/language_models.py b/src/lang/language_models.py new file mode 100644 index 0000000..e99a54b --- /dev/null +++ b/src/lang/language_models.py @@ -0,0 +1,56 @@ +# generated by datamodel-codegen: +# filename: de.yaml +# timestamp: 2026-08-17T18:44:37+00:00 + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class LongName: + train_from: str + train_via: str + + +@dataclass +class Channel: + status: str + long_name: LongName + + +@dataclass +class Footer: + notice: str + + +@dataclass +class Info: + description: str + via: str + via_and: str + next_stop: str + + +@dataclass +class EndOfConnection: + message: str + + +@dataclass +class Announcement: + title: str + end_of_connection: EndOfConnection + + +@dataclass +class Embeds: + footer: Footer + info: Info + announcement: Announcement + + +@dataclass +class Model: + channel: Channel + embeds: Embeds diff --git a/src/lang/locales.py b/src/lang/locales.py new file mode 100644 index 0000000..586594d --- /dev/null +++ b/src/lang/locales.py @@ -0,0 +1,34 @@ +import yaml +import sys + +from src.utils import logger +from src.config import config +from src.lang.language_models import Model + +class AutoFormatStr(str): + def __call__(self): + frame = sys._getframe(1) + context = {**frame.f_globals, **frame.f_locals} + return self.format(**context) + +class LanguageObject: + def __init__(self, dictionary: dict): + for key, value in dictionary.items(): + if isinstance(value, dict): + setattr(self, key, LanguageObject(value)) + elif isinstance(value, str): + setattr(self, key, AutoFormatStr(value)) + else: + setattr(self, key, value) + +def load_language(lang_code): + path = f"src/data/locales/{lang_code}.yaml" + + try: + with open(path, "r", encoding="utf-8") as file: + data = yaml.safe_load(file) + return LanguageObject(data) + except Exception as e: + logger(f"Failed loading language file: {e}", "FATAL") + +lang: Model = load_language(config.discord.lang) \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index 26d96b0..ef58390 100644 --- a/src/utils.py +++ b/src/utils.py @@ -160,7 +160,7 @@ def get_next_station(stops: dict, train_from: str) -> dict | None: return None -def format_via_list(stops: dict) -> str: +def format_via_list(stops: dict, via_and: str) -> str: if len(stops) > 2: stations = list(stops.keys()) trip_from = stations[0] @@ -173,7 +173,7 @@ def format_via_list(stops: dict) -> str: important_stops.remove(trip_to) if len(important_stops) > 1: - via = f"{', '.join(important_stops[:-1])} und {important_stops[-1]}" + via = f"{', '.join(important_stops[:-1])} {via_and} {important_stops[-1]}" else: via = important_stops[0] return via