diff --git a/Makefile.am b/Makefile.am
index d32fd5e9c..a52ad7aea 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,6 +10,7 @@ SUBDIRS = \
autoresolution \
autotimer \
babelzapper \
+ birthdayreminder \
blindscan \
bonjour \
btdevicesmanager \
diff --git a/birthdayreminder/CONTROL/control b/birthdayreminder/CONTROL/control
new file mode 100644
index 000000000..2e214798b
--- /dev/null
+++ b/birthdayreminder/CONTROL/control
@@ -0,0 +1,2 @@
+Description: Birthday Reminder: Reminds you of birthdays
+Depends: python-textutils
diff --git a/birthdayreminder/Makefile.am b/birthdayreminder/Makefile.am
new file mode 100644
index 000000000..5afb69089
--- /dev/null
+++ b/birthdayreminder/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = src po meta
diff --git a/birthdayreminder/meta/Makefile.am b/birthdayreminder/meta/Makefile.am
new file mode 100644
index 000000000..efc87b294
--- /dev/null
+++ b/birthdayreminder/meta/Makefile.am
@@ -0,0 +1,4 @@
+installdir = $(datadir)/meta/
+
+dist_install_DATA = plugin_birthdayreminder.xml
+
diff --git a/birthdayreminder/meta/plugin_birthdayreminder.xml b/birthdayreminder/meta/plugin_birthdayreminder.xml
new file mode 100644
index 000000000..17111bd5b
--- /dev/null
+++ b/birthdayreminder/meta/plugin_birthdayreminder.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Shaderman
+ Birthday Reminder
+ enigma2-plugin-extensions-birthdayreminder
+ Reminds you of birthdays
+ Reminds you of birthdays
+
+
+
+
+
+
diff --git a/birthdayreminder/po/BirthdayReminder.pot b/birthdayreminder/po/BirthdayReminder.pot
new file mode 100644
index 000000000..357c6b344
--- /dev/null
+++ b/birthdayreminder/po/BirthdayReminder.pot
@@ -0,0 +1,205 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-12 19:10+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr ""
+
+msgid "1 day"
+msgstr ""
+
+msgid "1 week"
+msgstr ""
+
+msgid "3 days"
+msgstr ""
+
+msgid "Add"
+msgstr ""
+
+msgid "Add a birthday"
+msgstr ""
+
+msgid "Add birthday"
+msgstr ""
+
+msgid "Age"
+msgstr ""
+
+msgid "Birthday"
+msgstr ""
+
+msgid "Birthday Reminder"
+msgstr ""
+
+msgid "Birthday Reminder Settings"
+msgstr ""
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr ""
+
+msgid "Birthday filename:"
+msgstr ""
+
+msgid "Birthdays"
+msgstr ""
+
+msgid "CSV import successful!"
+msgstr ""
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr ""
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr ""
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr ""
+
+msgid "Date format:"
+msgstr ""
+
+msgid "Day:"
+msgstr ""
+
+msgid "Disabled"
+msgstr ""
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr ""
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
+msgid "Edit Birthday Settings"
+msgstr ""
+
+msgid "Edit birthday"
+msgstr ""
+
+msgid "Edit birthdays"
+msgstr ""
+
+msgid "Edit the selected entry"
+msgstr ""
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+
+msgid "Exit the plugin"
+msgstr ""
+
+msgid "Export CSV file"
+msgstr ""
+
+msgid "Extras"
+msgstr ""
+
+msgid "Helps to remind of birthdays"
+msgstr ""
+
+msgid "Import CSV file"
+msgstr ""
+
+msgid "Invalid date!"
+msgstr ""
+
+msgid "Month:"
+msgstr ""
+
+msgid "Name"
+msgstr ""
+
+msgid "Name:"
+msgstr ""
+
+msgid "Networking port (default: 7374):"
+msgstr ""
+
+msgid "Next birthday"
+msgstr ""
+
+msgid "Notification time:"
+msgstr ""
+
+msgid "Open the extras menu"
+msgstr ""
+
+msgid "Path to birthday file:"
+msgstr ""
+
+msgid "Remind before birthday:"
+msgstr ""
+
+msgid "Remove"
+msgstr ""
+
+msgid "Remove the selected entry"
+msgstr ""
+
+msgid "Select a path for the birthday file"
+msgstr ""
+
+msgid "Show plugin in extensions menu:"
+msgstr ""
+
+msgid "Sort birthdays by:"
+msgstr ""
+
+msgid "Sorting next"
+msgstr ""
+
+msgid "Sorting previous"
+msgstr ""
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+
+msgid "What do you want to do?"
+msgstr ""
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr ""
+
+msgid "Year:"
+msgstr ""
diff --git a/birthdayreminder/po/Makefile.am b/birthdayreminder/po/Makefile.am
new file mode 100644
index 000000000..7f5cf7048
--- /dev/null
+++ b/birthdayreminder/po/Makefile.am
@@ -0,0 +1,2 @@
+PLUGIN = BirthdayReminder
+include $(top_srcdir)/Rules-po.mak
diff --git a/birthdayreminder/po/de.po b/birthdayreminder/po/de.po
new file mode 100644
index 000000000..9a71dad7f
--- /dev/null
+++ b/birthdayreminder/po/de.po
@@ -0,0 +1,231 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: BirthdayReminder 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-16 19:06+0430\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Mr.Servo \n"
+"Language-Team: \n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s wird %s in %s!"
+
+msgid "1 day"
+msgstr "1 Tag"
+
+msgid "1 week"
+msgstr "1 Woche"
+
+msgid "3 days"
+msgstr "3 Tage"
+
+msgid "Accept changes"
+msgstr "Änderungen übernehmen"
+
+msgid "Add"
+msgstr "Hinzufügen"
+
+msgid "Add a birthday"
+msgstr "Geburtstag hinzufügen"
+
+msgid "Add birthday"
+msgstr "Geburtstag hinzufügen"
+
+msgid "Age"
+msgstr "Alter"
+
+msgid "Birthday"
+msgstr "Geburtstag"
+
+msgid "Birthday Reminder"
+msgstr "Birthday Reminder"
+
+msgid "Birthday Reminder Settings"
+msgstr "Birthday Reminder Einstellungen"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Birthday Reminder hat %s Geburtstage von %s empfangen."
+
+msgid "Birthday filename:"
+msgstr "Dateiname:"
+
+msgid "Birthdays"
+msgstr "Geburtstage"
+
+msgid "CSV import successful!"
+msgstr "Die Daten der CSV- Datei wurden erfolgreich importiert!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Die CSV- Datei %s konnte nicht gefunden werden!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Die Daten der CSV- Datei %s konnten nicht importiert werden."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Die CSV- Datei %s konnte nicht gespeichert werden."
+
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgid "Change path"
+msgstr "Pfad ändern"
+
+msgid "Choose the filename:"
+msgstr "Wähle den Dateinamen:"
+
+msgid "Date format:"
+msgstr "Datumsformat:"
+
+msgid "Day:"
+msgstr "Tag:"
+
+msgid "Disabled"
+msgstr "Deaktiviert"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Geburtstagsliste an andere Boxen verteilen"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Wollen Sie den Eintrag für %s wirklich löschen?"
+
+msgid "Edit"
+msgstr "Bearbeiten"
+
+msgid "Edit birthday"
+msgstr "Geburtstag bearbeiten"
+
+msgid "Edit birthdays"
+msgstr "Geburtstage bearbeiten"
+
+msgid "Edit the selected entry"
+msgstr "Gewählten Eintrag bearbeiten"
+
+msgid "Enter the name of the person:"
+msgstr "Geben den Namen der Person ein:"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Fehler beim Lesen der Datei %s.\n"
+"\n"
+"Fehler: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Fehler beim Schreiben der Datei %s.\n"
+"\n"
+"Fehler: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Plugin beenden"
+
+msgid "Export CSV file"
+msgstr "CSV- Datei exportieren"
+
+msgid "Extras"
+msgstr "Extras"
+
+msgid "Helps to remind of birthdays"
+msgstr "Erinnert an Geburtstage"
+
+msgid "Import CSV file"
+msgstr "CSV- Datei importieren"
+
+msgid "Invalid Location"
+msgstr "Fehlerhafter Ort"
+
+msgid "Invalid date!"
+msgstr "Ungültiges Datum!"
+
+msgid "Month:"
+msgstr "Monat:"
+
+msgid "Name"
+msgstr "Name"
+
+msgid "Name:"
+msgstr "Name:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Netzwerk Port (Standard: 7374):"
+
+msgid "Next birthday"
+msgstr "Nächstem Geburtstag"
+
+msgid "Notification time:"
+msgstr "Zeit für Benachrichtigungen:"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Open the extras menu"
+msgstr "Extras menu öffnen"
+
+msgid "Path to birthday file:"
+msgstr "Pfad zur Geburtstagsdatei:"
+
+msgid "Remind before birthday:"
+msgstr "Vor Geburtstag erinnern:"
+
+msgid "Remove"
+msgstr "Entfernen"
+
+msgid "Remove the selected entry"
+msgstr "Gewählten Eintrag löschen"
+
+msgid "Save"
+msgstr "Speichern"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Zeige Plugin im Erweiterungsmenu:"
+
+msgid "Sort birthdays by:"
+msgstr "Sortiere Geburtstage nach:"
+
+msgid "Sorting next"
+msgstr "Sortierung vor"
+
+msgid "Sorting previous"
+msgstr "Sortierung zurück"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Heute ist %s's Geburtstag!\n"
+"\n"
+"Sie/er wurde am %s geboren und ist jetzt %s Jahr(e) alt."
+
+msgid "VirtualKeyBoard"
+msgstr "VirtualKeyBoard"
+
+msgid "What do you want to do?"
+msgstr "Was wollen Sie tun?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "Die CSV- Datei %s wurde gespeichert."
+
+msgid "Year:"
+msgstr "Jahr:"
diff --git a/birthdayreminder/po/en.po b/birthdayreminder/po/en.po
new file mode 100644
index 000000000..5f738af1a
--- /dev/null
+++ b/birthdayreminder/po/en.po
@@ -0,0 +1,236 @@
+# English translations for enigma2-plugins package.
+# Copyright (C) 2012 THE enigma2-plugins'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the enigma2-plugins package.
+# Automatically generated, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: enigma2-plugins 3.2.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-16 19:06+0430\n"
+"PO-Revision-Date: 2023-03-30 11:44+0200\n"
+"Last-Translator: Mr.Servo \n"
+"Language-Team: none\n"
+"Language: en\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s will turn %s in %s!"
+
+msgid "1 day"
+msgstr "1 day"
+
+msgid "1 week"
+msgstr "1 week"
+
+msgid "3 days"
+msgstr "3 days"
+
+msgid "Accept changes"
+msgstr "Accept changes"
+
+msgid "Add"
+msgstr "Add"
+
+msgid "Add a birthday"
+msgstr "Add a birthday"
+
+msgid "Add birthday"
+msgstr "Add birthday"
+
+msgid "Age"
+msgstr "Age"
+
+msgid "Birthday"
+msgstr "Birthday"
+
+msgid "Birthday Reminder"
+msgstr "Birthday Reminder"
+
+msgid "Birthday Reminder Settings"
+msgstr "Birthday Reminder Settings"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Birthday Reminder received %s birthdays from %s."
+
+msgid "Birthday filename:"
+msgstr "Birthday filename:"
+
+msgid "Birthdays"
+msgstr "Birthdays"
+
+msgid "CSV import successful!"
+msgstr "CSV import successful!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Can't find CSV file %s!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Can't import CSV data from file %s."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Can't write CSV file %s."
+
+msgid "Cancel"
+msgstr "Cancel"
+
+msgid "Change path"
+msgstr "Change path"
+
+msgid "Choose the filename:"
+msgstr "Choose the filename:"
+
+msgid "Date format:"
+msgstr "Date format:"
+
+msgid "Day:"
+msgstr "Day:"
+
+msgid "Disabled"
+msgstr "Disabled"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Distribute birthdays to other boxes"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Do you really want to delete the entry for %s?"
+
+msgid "Edit"
+msgstr "Edit"
+
+msgid "Edit birthday"
+msgstr "Edit birthday"
+
+msgid "Edit birthdays"
+msgstr "Edit birthdays"
+
+msgid "Edit the selected entry"
+msgstr "Edit the selected entry"
+
+msgid "Enter the name of the person:"
+msgstr "Enter the name of the person:"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Exit the plugin"
+
+msgid "Export CSV file"
+msgstr "Export CSV file"
+
+msgid "Extras"
+msgstr "Extras"
+
+msgid "Helps to remind of birthdays"
+msgstr "Helps to remind of birthdays"
+
+msgid "Import CSV file"
+msgstr "Import CSV file"
+
+msgid "Invalid Location"
+msgstr "Invalid Location"
+
+msgid "Invalid date!"
+msgstr "Invalid date!"
+
+msgid "Month:"
+msgstr "Month:"
+
+msgid "Name"
+msgstr "Name"
+
+msgid "Name:"
+msgstr "Name:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Networking port (default: 7374):"
+
+msgid "Next birthday"
+msgstr "Next birthday"
+
+msgid "Notification time:"
+msgstr "Notification time:"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Open the extras menu"
+msgstr "Open the extras menu"
+
+msgid "Path to birthday file:"
+msgstr "Path to birthday file:"
+
+msgid "Remind before birthday:"
+msgstr "Remind before birthday:"
+
+msgid "Remove"
+msgstr "Remove"
+
+msgid "Remove the selected entry"
+msgstr "Remove the selected entry"
+
+msgid "Save"
+msgstr "Save"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Show plugin in extensions menu:"
+
+msgid "Sort birthdays by:"
+msgstr "Sort birthdays by:"
+
+msgid "Sorting next"
+msgstr "Sorting next"
+
+msgid "Sorting previous"
+msgstr "Sorting previous"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+
+msgid "VirtualKeyBoard"
+msgstr "VirtualKeyBoard"
+
+msgid "What do you want to do?"
+msgstr "What do you want to do?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "Wrote CSV file %s."
+
+msgid "Year:"
+msgstr "Year:"
diff --git a/birthdayreminder/po/en_GB.po b/birthdayreminder/po/en_GB.po
new file mode 100644
index 000000000..4df837383
--- /dev/null
+++ b/birthdayreminder/po/en_GB.po
@@ -0,0 +1,236 @@
+# English translations for enigma2-plugins package.
+# Copyright (C) 2012 THE enigma2-plugins'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the enigma2-plugins package.
+# Automatically generated, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: enigma2-plugins 3.2.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-16 19:06+0430\n"
+"PO-Revision-Date: 2023-03-30 11:45+0200\n"
+"Last-Translator: Mr.Servo \n"
+"Language-Team: none\n"
+"Language: en_GB\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s will turn %s in %s!"
+
+msgid "1 day"
+msgstr "1 day"
+
+msgid "1 week"
+msgstr "1 week"
+
+msgid "3 days"
+msgstr "3 days"
+
+msgid "Accept changes"
+msgstr "Accept changes"
+
+msgid "Add"
+msgstr "Add"
+
+msgid "Add a birthday"
+msgstr "Add a birthday"
+
+msgid "Add birthday"
+msgstr "Add birthday"
+
+msgid "Age"
+msgstr "Age"
+
+msgid "Birthday"
+msgstr "Birthday"
+
+msgid "Birthday Reminder"
+msgstr "Birthday Reminder"
+
+msgid "Birthday Reminder Settings"
+msgstr "Birthday Reminder Settings"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Birthday Reminder received %s birthdays from %s."
+
+msgid "Birthday filename:"
+msgstr "Birthday filename:"
+
+msgid "Birthdays"
+msgstr "Birthdays"
+
+msgid "CSV import successful!"
+msgstr "CSV import successful!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Can't find CSV file %s!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Can't import CSV data from file %s."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Can't write CSV file %s."
+
+msgid "Cancel"
+msgstr "Cancel"
+
+msgid "Change path"
+msgstr "Change path"
+
+msgid "Choose the filename:"
+msgstr "Choose the filename:"
+
+msgid "Date format:"
+msgstr "Date format:"
+
+msgid "Day:"
+msgstr "Day:"
+
+msgid "Disabled"
+msgstr "Disabled"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Distribute birthdays to other boxes"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Do you really want to delete the entry for %s?"
+
+msgid "Edit"
+msgstr "Edit"
+
+msgid "Edit birthday"
+msgstr "Edit birthday"
+
+msgid "Edit birthdays"
+msgstr "Edit birthdays"
+
+msgid "Edit the selected entry"
+msgstr "Edit the selected entry"
+
+msgid "Enter the name of the person:"
+msgstr "Enter the name of the person:"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Exit the plugin"
+
+msgid "Export CSV file"
+msgstr "Export CSV file"
+
+msgid "Extras"
+msgstr "Extras"
+
+msgid "Helps to remind of birthdays"
+msgstr "Helps to remind of birthdays"
+
+msgid "Import CSV file"
+msgstr "Import CSV file"
+
+msgid "Invalid Location"
+msgstr "Invalid Location"
+
+msgid "Invalid date!"
+msgstr "Invalid date!"
+
+msgid "Month:"
+msgstr "Month:"
+
+msgid "Name"
+msgstr "Name"
+
+msgid "Name:"
+msgstr "Name:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Networking port (default: 7374):"
+
+msgid "Next birthday"
+msgstr "Next birthday"
+
+msgid "Notification time:"
+msgstr "Notification time:"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Open the extras menu"
+msgstr "Open the extras menu"
+
+msgid "Path to birthday file:"
+msgstr "Path to birthday file:"
+
+msgid "Remind before birthday:"
+msgstr "Remind before birthday:"
+
+msgid "Remove"
+msgstr "Remove"
+
+msgid "Remove the selected entry"
+msgstr "Remove the selected entry"
+
+msgid "Save"
+msgstr "Save"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Show plugin in extensions menu:"
+
+msgid "Sort birthdays by:"
+msgstr "Sort birthdays by:"
+
+msgid "Sorting next"
+msgstr "Sorting next"
+
+msgid "Sorting previous"
+msgstr "Sorting previous"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+
+msgid "VirtualKeyBoard"
+msgstr "VirtualKeyBoard"
+
+msgid "What do you want to do?"
+msgstr "What do you want to do?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "Wrote CSV file %s."
+
+msgid "Year:"
+msgstr "Year:"
diff --git a/birthdayreminder/po/fr.po b/birthdayreminder/po/fr.po
new file mode 100644
index 000000000..f9c215ac2
--- /dev/null
+++ b/birthdayreminder/po/fr.po
@@ -0,0 +1,236 @@
+# English translations for enigma2-plugins package.
+# Copyright (C) 2012 THE enigma2-plugins'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the enigma2-plugins package.
+# Automatically generated, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: enigma2-plugins 3.2.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-16 19:06+0430\n"
+"PO-Revision-Date: 2023-03-30 11:46+0200\n"
+"Last-Translator: Mr.Servo \n"
+"Language-Team: french\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s tournera %s dans %s!"
+
+msgid "1 day"
+msgstr "1 jour"
+
+msgid "1 week"
+msgstr "1 semaine"
+
+msgid "3 days"
+msgstr "3 jours"
+
+msgid "Accept changes"
+msgstr "Accepter les changements"
+
+msgid "Add"
+msgstr "Ajouter"
+
+msgid "Add a birthday"
+msgstr "Ajouter un anniversaire"
+
+msgid "Add birthday"
+msgstr "Ajouter anniversaire"
+
+msgid "Age"
+msgstr "Âge"
+
+msgid "Birthday"
+msgstr "Anniversaire"
+
+msgid "Birthday Reminder"
+msgstr "Birthday Reminder"
+
+msgid "Birthday Reminder Settings"
+msgstr "Birthday Reminder Configuration"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Birthday Reminder a reçu %s anniversaire de %s."
+
+msgid "Birthday filename:"
+msgstr "Nom du fichier Anniversaire:"
+
+msgid "Birthdays"
+msgstr "Anniversaires"
+
+msgid "CSV import successful!"
+msgstr "CSV importé avec succès!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Impossible de trouver un fichier CSV %s!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Impossible d'importer des données du fichier CSV %s."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Impossible d'écrire un fichier CSV %s."
+
+msgid "Cancel"
+msgstr "Annuler"
+
+msgid "Change path"
+msgstr "Changer le chemin"
+
+msgid "Choose the filename:"
+msgstr "Choisissez le nom du fichier:"
+
+msgid "Date format:"
+msgstr "Format date:"
+
+msgid "Day:"
+msgstr "Jour:"
+
+msgid "Disabled"
+msgstr "Désactivé"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Distribuer les anniversaires aux autres récepteurs"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Voulez-vous vraiment effacer l'entrée pour %s?"
+
+msgid "Edit"
+msgstr "Éditer"
+
+msgid "Edit birthday"
+msgstr "Éditer anniversaire"
+
+msgid "Edit birthdays"
+msgstr "Éditer anniversaires"
+
+msgid "Edit the selected entry"
+msgstr "Éditer l'entrée sélectionnée"
+
+msgid "Enter the name of the person:"
+msgstr "Saisissez le nom de la personne:"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Erreur en lisant le fichier %s.\n"
+"\n"
+"Erreur: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Erreur en écrivant le fichier %s.\n"
+"\n"
+"Erreur: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Quitter le plugin"
+
+msgid "Export CSV file"
+msgstr "Exporter un fichier CSV"
+
+msgid "Extras"
+msgstr "Extras"
+
+msgid "Helps to remind of birthdays"
+msgstr "Aide pour se rappeler les anniversaires"
+
+msgid "Import CSV file"
+msgstr "Importer un fichier CSV"
+
+msgid "Invalid Location"
+msgstr "Localisation non valide"
+
+msgid "Invalid date!"
+msgstr "Date non valide!"
+
+msgid "Month:"
+msgstr "Mois:"
+
+msgid "Name"
+msgstr "Nom"
+
+msgid "Name:"
+msgstr "Nom:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Port réseau (défaut: 7374):"
+
+msgid "Next birthday"
+msgstr "Anniversaire suivant"
+
+msgid "Notification time:"
+msgstr "Heure de notification:"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Open the extras menu"
+msgstr "Ouvrir le menu extras"
+
+msgid "Path to birthday file:"
+msgstr "Chemin vers le fichier des anniversaires:"
+
+msgid "Remind before birthday:"
+msgstr "Rappeler avant l'anniversaire:"
+
+msgid "Remove"
+msgstr "Enlever"
+
+msgid "Remove the selected entry"
+msgstr "Enlever l'entrée sélectionnée"
+
+msgid "Save"
+msgstr "Sauvegarder"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Afficher plugin dans le menu extensions:"
+
+msgid "Sort birthdays by:"
+msgstr "Afficher les anniversaires par:"
+
+msgid "Sorting next"
+msgstr "Tri suivant"
+
+msgid "Sorting previous"
+msgstr "Tri précédent"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Aujourd'hui c'est l'anniversaire de %s!\n"
+"\n"
+"Il/Elle est né(e) en %s et a donc %s an(s)."
+
+msgid "VirtualKeyBoard"
+msgstr "VirtualKeyBoard"
+
+msgid "What do you want to do?"
+msgstr "Que voulez-vous faire?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "Écrire un fichier CSV %s."
+
+msgid "Year:"
+msgstr "Année:"
diff --git a/birthdayreminder/po/it.po b/birthdayreminder/po/it.po
new file mode 100644
index 000000000..44dc1554a
--- /dev/null
+++ b/birthdayreminder/po/it.po
@@ -0,0 +1,235 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: enigma2 plugin - birthday reminder\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-16 19:06+0430\n"
+"PO-Revision-Date: 2023-03-30 11:49+0200\n"
+"Last-Translator: Mr.Servo \n"
+"Language-Team: www.linsat.net \n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-KeywordsList: _;gettext;gettext_noop\n"
+"X-Poedit-Basepath: /media/TREKSTOR/enigma2/BirthdayReminder/BirthdayReminder\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Generator: Poedit 3.2.2\n"
+"X-Poedit-SearchPath-0: /media/TREKSTOR/enigma2/BirthdayReminder/BirthdayReminder\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s compirà %s anni fra %s!"
+
+msgid "1 day"
+msgstr "1 giorno"
+
+msgid "1 week"
+msgstr "1 settimana"
+
+msgid "3 days"
+msgstr "3 giorni"
+
+msgid "Accept changes"
+msgstr "Conf. modifiche"
+
+msgid "Add"
+msgstr "Agg"
+
+msgid "Add a birthday"
+msgstr "Agg. compleanno"
+
+msgid "Add birthday"
+msgstr "Aggiungere compleanno"
+
+msgid "Age"
+msgstr "Età"
+
+msgid "Birthday"
+msgstr "Compleanno"
+
+msgid "Birthday Reminder"
+msgstr "Birthday Reminder"
+
+msgid "Birthday Reminder Settings"
+msgstr "Configurazione Birthday Reminder"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Birthday Reminder: ricevuti %s compleanni da %s."
+
+msgid "Birthday filename:"
+msgstr "Nome file compleanni:"
+
+msgid "Birthdays"
+msgstr "Compleanni"
+
+msgid "CSV import successful!"
+msgstr "Importazione CVS corretta!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Impossibile trovare file CVS %s!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Impossible importare dati dal file %s."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Impossibile scrivere file CVS %s."
+
+msgid "Cancel"
+msgstr "Annullare"
+
+msgid "Change path"
+msgstr "Mod. percorso"
+
+msgid "Choose the filename:"
+msgstr "Scegliere il nome del file:"
+
+msgid "Date format:"
+msgstr "Formato data:"
+
+msgid "Day:"
+msgstr "Giorno:"
+
+msgid "Disabled"
+msgstr "Disabilitato"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Distribuire compleanni ad altri box"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "%s: RIMUOVERE la voce?"
+
+msgid "Edit"
+msgstr "Mod"
+
+msgid "Edit birthday"
+msgstr "Modificare compleanno"
+
+msgid "Edit birthdays"
+msgstr "Mod. compleanni"
+
+msgid "Edit the selected entry"
+msgstr "Mod. voce selezionata"
+
+msgid "Enter the name of the person:"
+msgstr "Inserire il nome della persona:"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"%s: errore in lettura file.\n"
+"\n"
+"Errore: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"%s: errore in scrittura file.\n"
+"\n"
+"Errore: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Uscire"
+
+msgid "Export CSV file"
+msgstr "Esportare come CVS"
+
+msgid "Extras"
+msgstr "Extra"
+
+msgid "Helps to remind of birthdays"
+msgstr "Un aiuto per ricordare i compleanni"
+
+msgid "Import CSV file"
+msgstr "Importare CVS"
+
+msgid "Invalid Location"
+msgstr "Estinazione non valida"
+
+msgid "Invalid date!"
+msgstr "Data non valida!"
+
+msgid "Month:"
+msgstr "Mese:"
+
+msgid "Name"
+msgstr "Nome"
+
+msgid "Name:"
+msgstr "Nome:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Porta (predefinita: 7374):"
+
+msgid "Next birthday"
+msgstr "Prossimo compleanno"
+
+msgid "Notification time:"
+msgstr "Ora notifica:"
+
+msgid "OK"
+msgstr "Ok"
+
+msgid "Open the extras menu"
+msgstr "Aprire menu extra"
+
+msgid "Path to birthday file:"
+msgstr "Percorso file compleanni:"
+
+msgid "Remind before birthday:"
+msgstr "Avvisare con un anticipo di:"
+
+msgid "Remove"
+msgstr "Rimuov"
+
+msgid "Remove the selected entry"
+msgstr "Rimuov. voce selezionata"
+
+msgid "Save"
+msgstr "Salvare"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Mostrare nel menu estensioni:"
+
+msgid "Sort birthdays by:"
+msgstr "Ordinare per:"
+
+msgid "Sorting next"
+msgstr "Crescente"
+
+msgid "Sorting previous"
+msgstr "Decrescente"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Oggi è il compleanno di %s!\n"
+"\n"
+"Data di nascita: %s - Età: %s."
+
+msgid "VirtualKeyBoard"
+msgstr "Tastiera virtuale"
+
+msgid "What do you want to do?"
+msgstr "Cosa si desidera fare?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "Scritto file CVS %s."
+
+msgid "Year:"
+msgstr "Anno:"
diff --git a/birthdayreminder/po/lt.po b/birthdayreminder/po/lt.po
new file mode 100644
index 000000000..7590ce1e0
--- /dev/null
+++ b/birthdayreminder/po/lt.po
@@ -0,0 +1,236 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-16 19:06+0430\n"
+"PO-Revision-Date: 2023-03-30 11:49+0200\n"
+"Last-Translator: Mr.Servo \n"
+"Language-Team: \n"
+"Language: lt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Generated-By: pygettext.py 1.5\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s įjungs %s po %s!"
+
+msgid "1 day"
+msgstr "1 dieną"
+
+msgid "1 week"
+msgstr "1 savaitę"
+
+msgid "3 days"
+msgstr "3 dienas"
+
+msgid "Accept changes"
+msgstr "Priimti pakeitimus"
+
+msgid "Add"
+msgstr "Pridėti"
+
+msgid "Add a birthday"
+msgstr "Pridėti gimtadienį"
+
+msgid "Add birthday"
+msgstr "Pridėti gimtadienį"
+
+msgid "Age"
+msgstr "Amžius"
+
+msgid "Birthday"
+msgstr "Gimtadienis"
+
+msgid "Birthday Reminder"
+msgstr "Gimtadienių priminimas"
+
+msgid "Birthday Reminder Settings"
+msgstr "Gimtadienių priminimo nustatymai"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Gimtadienių priminimo parsiųsti %s gimtadieniai iš %s."
+
+msgid "Birthday filename:"
+msgstr "Gimtadienių failo pavadinimas:"
+
+msgid "Birthdays"
+msgstr "Gimtadieniai"
+
+msgid "CSV import successful!"
+msgstr "CSV importas pavyko!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Surasti CSV failo %s nepavyko!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Importuoti CSV duomenų iš failo %s negalima."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Įrašyti CSV failo %s nepavyko."
+
+msgid "Cancel"
+msgstr "Atšaukti"
+
+msgid "Change path"
+msgstr "Keisti kelią"
+
+msgid "Choose the filename:"
+msgstr "Pasirinkite failo pavadinimą:"
+
+msgid "Date format:"
+msgstr "Datos formatas:"
+
+msgid "Day:"
+msgstr "Diena:"
+
+msgid "Disabled"
+msgstr "Išjungta"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Platinti gimtadienius į kitus aparatus"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Ar tikrai norite trinti įrašą %s?"
+
+msgid "Edit"
+msgstr "Redaguoti"
+
+msgid "Edit birthday"
+msgstr "Redaguoti gimtadienį"
+
+msgid "Edit birthdays"
+msgstr "Redaguoti gimtadienius"
+
+msgid "Edit the selected entry"
+msgstr "Redaguoti pasirinktą įrašą"
+
+msgid "Enter the name of the person:"
+msgstr "Įveskite asmens vardą ir pavardę:"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Klaida skaitant failą %s.\n"
+"\n"
+"Klaida: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Klaida rašant failą %s.\n"
+"\n"
+"Klaida: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Užverti įskiepį"
+
+msgid "Export CSV file"
+msgstr "Eksportuoti CSV failą"
+
+msgid "Extras"
+msgstr "Priedai"
+
+msgid "Helps to remind of birthdays"
+msgstr "Padeda prisiminti apie gimtadienius"
+
+msgid "Import CSV file"
+msgstr "Importuoti CSV failą"
+
+msgid "Invalid Location"
+msgstr "Neteisinga vieta"
+
+msgid "Invalid date!"
+msgstr "Neleistina data!"
+
+msgid "Month:"
+msgstr "Mėnuo:"
+
+msgid "Name"
+msgstr "Vardas"
+
+msgid "Name:"
+msgstr "Vardas:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Tinklo prievadas (numatytasis: 7374):"
+
+msgid "Next birthday"
+msgstr "Kitas gimtadienis"
+
+msgid "Notification time:"
+msgstr "Pranešimo laikas:"
+
+msgid "OK"
+msgstr "Gerai"
+
+msgid "Open the extras menu"
+msgstr "Atidaryti priedų meniu"
+
+msgid "Path to birthday file:"
+msgstr "Kelias į gimtadienių failą:"
+
+msgid "Remind before birthday:"
+msgstr "Priminti prieš gimtadienį:"
+
+msgid "Remove"
+msgstr "Pašalinti"
+
+msgid "Remove the selected entry"
+msgstr "Pašalinti pasirinktą įrašą"
+
+msgid "Save"
+msgstr "Išsaugoti"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Rodyti tarp plėtinių meniu:"
+
+msgid "Sort birthdays by:"
+msgstr "Rūšiuoti gimtadienius pagal:"
+
+msgid "Sorting next"
+msgstr "Kitas rūšiavimas"
+
+msgid "Sorting previous"
+msgstr "Ankstesnis rūšiavimas"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Šiandien %s gimtadienis!\n"
+"\n"
+"Gimė %s ir dabar yra %s metų amžiaus."
+
+msgid "VirtualKeyBoard"
+msgstr "VirtualKeyBoard"
+
+msgid "What do you want to do?"
+msgstr "Ką norite daryti?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "Įrašytas CSV failas %s."
+
+msgid "Year:"
+msgstr "Metai:"
diff --git a/birthdayreminder/po/nl.po b/birthdayreminder/po/nl.po
new file mode 100644
index 000000000..adb553a58
--- /dev/null
+++ b/birthdayreminder/po/nl.po
@@ -0,0 +1,236 @@
+# Dutch translations for enigma2-plugins package.
+# Copyright (C) 2012 THE enigma2-plugins'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the enigma2-plugins package.
+# Automatically generated, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: enigma2-plugins 3.2.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-04-16 19:06+0430\n"
+"PO-Revision-Date: 2023-03-30 11:41+0200\n"
+"Last-Translator: Mr.Servo \n"
+"Language-Team: Andy Blackburn/Rob van der Does\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s zal worden gewijzigd %s in %s!"
+
+msgid "1 day"
+msgstr "1 dag"
+
+msgid "1 week"
+msgstr "1 week"
+
+msgid "3 days"
+msgstr "3 dagen"
+
+msgid "Accept changes"
+msgstr "Accepteer veranderingen"
+
+msgid "Add"
+msgstr "Voeg toe"
+
+msgid "Add a birthday"
+msgstr "Voeg een verjaardag toe"
+
+msgid "Add birthday"
+msgstr "Voeg verjaardag toe"
+
+msgid "Age"
+msgstr "Leeftijd"
+
+msgid "Birthday"
+msgstr "Verjaardag"
+
+msgid "Birthday Reminder"
+msgstr "Verjaardagsherinnering"
+
+msgid "Birthday Reminder Settings"
+msgstr "Verjaardagsherinnering instellingen"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Verjaardagsherinnering ontvangen %s verjaardagen van %s."
+
+msgid "Birthday filename:"
+msgstr "Verjaardagen bestandsnaam:"
+
+msgid "Birthdays"
+msgstr "Verjaardagen"
+
+msgid "CSV import successful!"
+msgstr "CSV import geslaagd!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Kan CSV bestand %s niet vinden!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Kan CSV data van bestand %s niet importeren."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Kan CSV bestand %s niet schrijven."
+
+msgid "Cancel"
+msgstr "Annuleer"
+
+msgid "Change path"
+msgstr "Verander pad"
+
+msgid "Choose the filename:"
+msgstr "Kies de bestandsnaam:"
+
+msgid "Date format:"
+msgstr "Datum formaat:"
+
+msgid "Day:"
+msgstr "Dag:"
+
+msgid "Disabled"
+msgstr "Uitgeschakeld"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Geef verjaardagen door aan andere ontvangers"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Wilt u echt het bestand voor %s verwijderen?"
+
+msgid "Edit"
+msgstr "Bewerk"
+
+msgid "Edit birthday"
+msgstr "Bewerk verjaardag"
+
+msgid "Edit birthdays"
+msgstr "Bewerk verjaardagen"
+
+msgid "Edit the selected entry"
+msgstr "Bewerk de geselecteerde waarde"
+
+msgid "Enter the name of the person:"
+msgstr "Geef de naam van de persoon:"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Fout in lezen bestand %s.\n"
+"\n"
+"Fout: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Fout in schrijven bestand %s.\n"
+"\n"
+"Fout: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Verlaat de plugin"
+
+msgid "Export CSV file"
+msgstr "Exporteer CSV bestand"
+
+msgid "Extras"
+msgstr "Extras"
+
+msgid "Helps to remind of birthdays"
+msgstr "Helpt te herinneren aan verjaardagen"
+
+msgid "Import CSV file"
+msgstr "Importeer CSV bestand"
+
+msgid "Invalid Location"
+msgstr "Ongeldige lokatie"
+
+msgid "Invalid date!"
+msgstr "Ongeldige datum!"
+
+msgid "Month:"
+msgstr "Maand:"
+
+msgid "Name"
+msgstr "Naam"
+
+msgid "Name:"
+msgstr "Naam:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Netwerk poort (default: 7374):"
+
+msgid "Next birthday"
+msgstr "Volgende verjaardag"
+
+msgid "Notification time:"
+msgstr "Waarschuwingstijd:"
+
+msgid "OK"
+msgstr "OK"
+
+msgid "Open the extras menu"
+msgstr "Open het extras menu"
+
+msgid "Path to birthday file:"
+msgstr "Pad naar verjaardagen bestand:"
+
+msgid "Remind before birthday:"
+msgstr "Herinner voor de verjaardag:"
+
+msgid "Remove"
+msgstr "Verwijder"
+
+msgid "Remove the selected entry"
+msgstr "Verwijder de geselecteerde waarde"
+
+msgid "Save"
+msgstr "Sla op"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Toon plugin in extensies:"
+
+msgid "Sort birthdays by:"
+msgstr "Sorteer verjaardagen volgens:"
+
+msgid "Sorting next"
+msgstr "Volgende sortering"
+
+msgid "Sorting previous"
+msgstr "Vorige sortering"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Vandaag is %s's verjaardag!\n"
+"\n"
+"Hij/Zij is geboren op %s en is nu %s jaar oud."
+
+msgid "VirtualKeyBoard"
+msgstr "VirtualKeyBoard"
+
+msgid "What do you want to do?"
+msgstr "Wat wilt u doen?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "CSV bestand %s geschreven."
+
+msgid "Year:"
+msgstr "Jaar:"
diff --git a/birthdayreminder/po/pl.po b/birthdayreminder/po/pl.po
new file mode 100644
index 000000000..7183d8e33
--- /dev/null
+++ b/birthdayreminder/po/pl.po
@@ -0,0 +1,196 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: Birthday Reminder\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: 2023-03-30 09:32+0200\n"
+"Last-Translator: paps\n"
+"Language-Team: paps\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+msgid "%s will turn %s in %s!"
+msgstr "%s będzie miał/a %s urodziny za %s!"
+
+msgid "1 day"
+msgstr "1 dzień"
+
+msgid "1 week"
+msgstr "tydzień"
+
+msgid "3 days"
+msgstr "3 dni"
+
+msgid "Add"
+msgstr "Dodaj"
+
+msgid "Add a birthday"
+msgstr "Dodaj urodziny"
+
+msgid "Add birthday"
+msgstr "Dodaj urodziny"
+
+msgid "Age"
+msgstr "Wiek"
+
+msgid "Birthday"
+msgstr "Urodziny"
+
+msgid "Birthday Reminder"
+msgstr "Przypomnienie o urodzinach"
+
+msgid "Birthday Reminder Settings"
+msgstr "Ustawienia Birthday Reminder"
+
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Birthday Remainder odebrał %s wpisów urodzin od %s."
+
+msgid "Birthday filename:"
+msgstr "Nazwa pliku z urodzinami:"
+
+msgid "Birthdays"
+msgstr "Lista urodzin"
+
+msgid "CSV import successful!"
+msgstr "Pomyślnie zaimportowano plik CSV!"
+
+msgid "Can't find CSV file %s!"
+msgstr "Nie można znaleźć pliku CSV: %s!"
+
+msgid "Can't import CSV data from file %s."
+msgstr "Nie można zaimportować danych z pliku CSV: %s."
+
+msgid "Can't write CSV file %s."
+msgstr "Nie można zapisać pliku CSV: %s."
+
+msgid "Date format:"
+msgstr "Format daty:"
+
+msgid "Day:"
+msgstr "Dzień:"
+
+msgid "Disabled"
+msgstr "Wyłączone"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Wysyłanie pliku z urodzinami do innych STB"
+
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Czy na pewno chcesz usunąć wpis '%s'?"
+
+msgid "Edit"
+msgstr "Edycja"
+
+msgid "Edit birthday"
+msgstr "Edycja urodzin"
+
+msgid "Edit birthdays"
+msgstr "Edycja urodzin"
+
+msgid "Edit the selected entry"
+msgstr "Edycja zaznaczonego wpisu"
+
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Błąd odczytu pliku: %s.\n"
+"\n"
+"Błąd: %s, %s"
+
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Błąd zapisu pliku: %s.\n"
+"\n"
+"Błąd: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Wyjście"
+
+msgid "Export CSV file"
+msgstr "Eksport pliku CSV"
+
+msgid "Extras"
+msgstr "Dodatki"
+
+msgid "Helps to remind of birthdays"
+msgstr "Przypomienie o urodzinach"
+
+msgid "Import CSV file"
+msgstr "Import pliku CSV"
+
+msgid "Invalid date!"
+msgstr "Niepoprawna data!"
+
+msgid "Month:"
+msgstr "Miesiąc:"
+
+msgid "Name"
+msgstr "Nazwisko"
+
+msgid "Name:"
+msgstr "Nazwisko:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Port sieciowy (domyślnie: 7374)"
+
+msgid "Next birthday"
+msgstr "Następne urodziny"
+
+msgid "Notification time:"
+msgstr "Godzina przypomnienia:"
+
+msgid "Open the extras menu"
+msgstr "Otwórz dodatkowe menu"
+
+msgid "Path to birthday file:"
+msgstr "Ścieżka do pliku z urodzinami:"
+
+msgid "Remind before birthday:"
+msgstr "Przypomnij przed urodzinami:"
+
+msgid "Remove"
+msgstr "Usuń"
+
+msgid "Remove the selected entry"
+msgstr "Usuń zaznaczony wpis"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Pokaż wtyczkę w rozszerzonym menu:"
+
+msgid "Sort birthdays by:"
+msgstr ""
+"Sortowanie listy \n"
+"urodzin:"
+
+msgid "Sorting next"
+msgstr "Następne"
+
+msgid "Sorting previous"
+msgstr "Poprzednie"
+
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Dzisiaj %s ma urodziny!\n"
+"\n"
+"Urodził/a się w %s i ma %s lat/a."
+
+msgid "What do you want to do?"
+msgstr "Co chcesz zrobić?"
+
+msgid "Wrote CSV file %s."
+msgstr "Zapisano plik CSV: %s."
+
+msgid "Year:"
+msgstr "Rok:"
diff --git a/birthdayreminder/po/pt.po b/birthdayreminder/po/pt.po
new file mode 100644
index 000000000..32c72f423
--- /dev/null
+++ b/birthdayreminder/po/pt.po
@@ -0,0 +1,215 @@
+# Portuguese translations for enigma2-plugins package.
+# Copyright (C) 2012 THE enigma2-plugins'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the enigma2-plugins package.
+# Automatically generated, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2023-01-12 19:10+0100\n"
+"PO-Revision-Date: 2023-03-30 21:37+0100\n"
+"Last-Translator: NUNIGAIA \n"
+"Language-Team: \n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#, python-format
+msgid "%s will turn %s in %s!"
+msgstr "%s vai-se %s transformar em %s!"
+
+msgid "1 day"
+msgstr "1 dia"
+
+msgid "1 week"
+msgstr "1 semana"
+
+msgid "3 days"
+msgstr "3 dias"
+
+msgid "Add"
+msgstr "Adicionar"
+
+msgid "Add a birthday"
+msgstr "Adicionar um aniversário"
+
+msgid "Add birthday"
+msgstr "Adicionar aniversário"
+
+msgid "Age"
+msgstr "Idade"
+
+msgid "Birthday"
+msgstr "Aniversário"
+
+msgid "Birthday Reminder"
+msgstr "Lembrete de aniversário"
+
+msgid "Birthday Reminder Settings"
+msgstr "Definições do lembrete de aniversário"
+
+#, python-format
+msgid "Birthday Reminder received %s birthdays from %s."
+msgstr "Lembrete de aniversário recebido %s aniversários de %s."
+
+msgid "Birthday filename:"
+msgstr "Nome do arquivo de aniversário:"
+
+msgid "Birthdays"
+msgstr "Aniversários"
+
+msgid "CSV import successful!"
+msgstr "Importação de CSV bem-sucedida!"
+
+#, python-format
+msgid "Can't find CSV file %s!"
+msgstr "Não é possível encontrar o arquivo CSV %s!"
+
+#, python-format
+msgid "Can't import CSV data from file %s."
+msgstr "Não é possível importar dados CSV do arquivo %s."
+
+#, python-format
+msgid "Can't write CSV file %s."
+msgstr "Não é possível gravar o arquivo CSV %s."
+
+msgid "Date format:"
+msgstr "Formato da Data:"
+
+msgid "Day:"
+msgstr "Dia:"
+
+msgid "Disabled"
+msgstr "Inativo"
+
+msgid "Distribute birthdays to other Dreamboxes"
+msgstr "Distribuir aniversários para outros receptores"
+
+#, python-format
+msgid "Do you really want to delete the entry for %s?"
+msgstr "Você realmente deseja excluir a entrada para %s?"
+
+msgid "Edit"
+msgstr "Editar"
+
+msgid "Edit Birthday Settings"
+msgstr "Editar configurações de aniversário"
+
+msgid "Edit birthday"
+msgstr "Editar aniversário"
+
+msgid "Edit birthdays"
+msgstr "Editar aniversários"
+
+msgid "Edit the selected entry"
+msgstr "Editar a entrada selecionada"
+
+#, python-format
+msgid ""
+"Error reading file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Erro ao ler %s do arquivo.\n"
+"\n"
+"Erro: %s, %s"
+
+#, python-format
+msgid ""
+"Error writing file %s.\n"
+"\n"
+"Error: %s, %s"
+msgstr ""
+"Erro ao gravar %s de arquivo.\n"
+"\n"
+"Erro: %s, %s"
+
+msgid "Exit the plugin"
+msgstr "Sair do plugin"
+
+msgid "Export CSV file"
+msgstr "Exportar arquivo CSV"
+
+msgid "Extras"
+msgstr "Extras"
+
+msgid "Helps to remind of birthdays"
+msgstr "Ajuda a lembrar de aniversários"
+
+msgid "Import CSV file"
+msgstr "Importar arquivo CSV"
+
+msgid "Invalid date!"
+msgstr "Data inválida!"
+
+msgid "Month:"
+msgstr "Mês:"
+
+msgid "Name"
+msgstr "Nome"
+
+msgid "Name:"
+msgstr "Nome:"
+
+msgid "Networking port (default: 7374):"
+msgstr "Porta de rede (padrão: 7374):"
+
+msgid "Next birthday"
+msgstr "Próximo aniversário"
+
+msgid "Notification time:"
+msgstr "Hora da notificação:"
+
+msgid "Open the extras menu"
+msgstr "Abrir o menu de extras"
+
+msgid "Path to birthday file:"
+msgstr "Caminho para o arquivo de aniversário:"
+
+msgid "Remind before birthday:"
+msgstr "Lembrar antes do aniversário:"
+
+msgid "Remove"
+msgstr "Remover"
+
+msgid "Remove the selected entry"
+msgstr "Remover a entrada selecionada"
+
+msgid "Select a path for the birthday file"
+msgstr "Selecione um caminho para o arquivo de aniversário"
+
+msgid "Show plugin in extensions menu:"
+msgstr "Mostrar plugin no menu de extensões:"
+
+msgid "Sort birthdays by:"
+msgstr "Ordenar aniversários por:"
+
+msgid "Sorting next"
+msgstr "Ordenação seguinte"
+
+msgid "Sorting previous"
+msgstr "Ordenação anterior"
+
+#, python-format
+msgid ""
+"Today is %s's birthday!\n"
+"\n"
+"She/he was born on %s and is now %s year(s) old."
+msgstr ""
+"Hoje é aniversário de %s!\n"
+"\n"
+"Ele / ela nasceu em %s e agora tem %s ano (s) de idade."
+
+msgid "What do you want to do?"
+msgstr "O que pretende fazer?"
+
+#, python-format
+msgid "Wrote CSV file %s."
+msgstr "Escreveu o arquivo CSV %s."
+
+msgid "Year:"
+msgstr "Ano:"
diff --git a/birthdayreminder/src/BirthdayNetworking.py b/birthdayreminder/src/BirthdayNetworking.py
new file mode 100644
index 000000000..7de345c55
--- /dev/null
+++ b/birthdayreminder/src/BirthdayNetworking.py
@@ -0,0 +1,138 @@
+# PYTHON IMPORTS
+from pickle import loads as pickle_loads
+from six import ensure_binary
+from socket import SOL_SOCKET, SO_BROADCAST
+from twisted.internet.error import ConnectionDone
+from twisted.internet.protocol import DatagramProtocol, ServerFactory, ClientFactory, Protocol
+
+# ENIGMA IMPORTS
+from Components.config import config
+
+# for localized messages
+from . import _
+
+
+class BroadcastProtocol(DatagramProtocol): # this class handles UDP broadcasts (send/receive)
+ def __init__(self, parent):
+ self.parent = parent
+ self.port = config.plugins.birthdayreminder.broadcastPort.value
+ self.uuid = self.getNodeHack() # sent with broadcasts to identify ourselves when receiving our own broascast :o
+
+ def startProtocol(self):
+ if self.transport:
+ self.transport.socket.setsockopt(SOL_SOCKET, SO_BROADCAST, True)
+
+ def sendBroadcast(self, message):
+ if self.transport:
+ newMessage = ensure_binary(''.join([self.uuid, " ", message]))
+ self.transport.write(newMessage, ("255.255.255.255", self.port))
+
+ def datagramReceived(self, data, addr):
+ parts = data.split() # filter unknown data. we expect two parts, a uuid and a "command"
+ if len(parts) != 2:
+ return
+ if parts[0] == self.uuid: # ignore our own package
+ return
+ elif parts[1] == "offeringList": # a box is offering to send a list
+ print("[Birthday Reminder] received a list offer from %s" % addr[0])
+ self.parent.requestBirthdayList(addr)
+ elif parts[1] == "ping": # are we there?
+ print("[Birthday Reminder] received ping from %s" % addr[0])
+ self.parent.sendPingResponse(addr)
+
+ def getNodeHack(self):
+ from os import urandom
+ return str(urandom(16))
+
+
+class TransferServerProtocol(Protocol): # the server classes are used to send and receive birthday lists
+ def __init__(self, parent):
+ self.parent = parent
+
+ def connectionMade(self):
+ if self.transport:
+ print("[Birthday Reminder] client %s connected" % self.transport.getPeer().host)
+
+ def dataReceived(self, data):
+ peer = self.transport.getPeer().host if self.transport else None
+ if data == "requestingList":
+ print("[Birthday Reminder] sending birthday list to client %s" % peer)
+ data = self.parent.readRawFile()
+ if data and self.transport:
+ self.transport.write(data)
+ else: # should be a pickled birthday list...
+ receivedList = None
+ try: # let's see if it's pickled data
+ receivedList = pickle_loads(data)
+ print("[Birthday Reminder] received birthday list from %s" % peer)
+ except Exception as err:
+ print("[Birthday Reminder] received unknown package from %s" % peer)
+ if receivedList is None:
+ return
+ self.parent.writeRawFile(data)
+ self.parent.load()
+ self.parent.addAllTimers()
+ self.parent.showReceivedMessage(len(receivedList), peer)
+ if self.transport:
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ if self.transport:
+ if reason.type == ConnectionDone:
+ print("[Birthday Reminder] closed connection to client %s" % self.transport.getPeer().host)
+ else:
+ print("[Birthday Reminder] lost connection to client %s. Reason: %s" % (self.transport.getPeer().host, str(reason.value)))
+
+
+class TransferServerFactory(ServerFactory):
+ def __init__(self, parent):
+ self.parent = parent
+
+ def buildProtocol(self, addr):
+ return TransferServerProtocol(self.parent)
+
+
+class TransferClientProtocol(Protocol): # the client classes are used to request and receive birthday lists
+ def __init__(self, parent, data):
+ self.parent = parent
+ self.data = data
+
+ def dataReceived(self, data):
+ peer = self.transport.getPeer().host if self.transport else None
+ receivedList = None
+ try:
+ receivedList = pickle_loads(data)
+ print("[Birthday Reminder] received birthday list from %s" % peer)
+ except Exception as err:
+ print("[Birthday Reminder] received unknown package from %s" % peer)
+ if receivedList is None:
+ return
+ self.parent.save(receivedList) # save and load the received list
+ self.parent.load()
+ self.parent.addAllTimers()
+ self.parent.showReceivedMessage(len(receivedList), peer)
+
+ def connectionMade(self):
+ if self.transport:
+ self.transport.write(self.data)
+
+
+class TransferClientFactory(ClientFactory):
+ def __init__(self, parent, data):
+ self.parent = parent
+ self.data = data
+
+ def buildProtocol(self, addr):
+ return TransferClientProtocol(self.parent, self.data)
+
+ def startedConnecting(self, connector):
+ dest = ''.join([connector.getDestination().host, ":", str(connector.getDestination().port)])
+
+ def clientConnectionFailed(self, connector, reason):
+ print("[Birthday Reminder] connection to server %s failed. Reason: %s" % (connector.getDestination().host, str(reason.value)))
+
+ def clientConnectionLost(self, connector, reason):
+ if reason.type == ConnectionDone:
+ print("[Birthday Reminder] disconnected from server %s" % connector.getDestination().host)
+ else:
+ print("[Birthday Reminder] lost connection to server %s. Reason: %s" % (connector.getDestination().host, str(reason.value)))
diff --git a/birthdayreminder/src/BirthdayReminder.py b/birthdayreminder/src/BirthdayReminder.py
new file mode 100644
index 000000000..b098ae6c0
--- /dev/null
+++ b/birthdayreminder/src/BirthdayReminder.py
@@ -0,0 +1,497 @@
+# PYTHON IMPORTS
+from copy import copy
+from csv import writer as csv_writer, reader as csv_reader
+from datetime import datetime, date
+from os.path import isfile, join, split
+from pickle import dump, load
+from time import mktime, strptime
+from functools import cmp_to_key
+
+# ENIGMA IMPORTS
+from Components.ActionMap import HelpableActionMap
+from Components.config import config, NoSave, ConfigText, ConfigInteger, ConfigDirectory
+from Components.Label import Label
+from Components.Sources.List import List
+from Components.Sources.StaticText import StaticText
+from Screens.ChoiceBox import ChoiceBox
+from Screens.HelpMenu import HelpableScreen
+from Screens.MessageBox import MessageBox
+from Screens.Screen import Screen
+from skin import parseColor
+from Tools import Notifications
+from Screens.LocationBox import defaultInhibitDirs, LocationBox
+from Screens.Setup import Setup
+
+# for localized messages
+from . import _
+
+CSVFILE = "/tmp/birthdayreminder.csv"
+MODULE_NAME = __name__.split(".")[-1]
+
+
+class BirthdayStore:
+ def __init__(self):
+ self.loadStore()
+
+ def readRawFile(self):
+ data = None
+ fileName = config.plugins.birthdayreminder.file.value
+ if isfile(fileName):
+ try:
+ with open(fileName, "r") as f:
+ data = f.read()
+ except IOError as err:
+ (error_no, error_str) = err.args
+ print("[%s] ERROR reading from file %s. Error: %s, %s" % (MODULE_NAME, fileName, error_no, error_str))
+ text = _("Error reading file %s.\n\nError: %s, %s") % (fileName, error_no, error_str)
+ Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=3)
+ return data
+
+ def writeRawFile(self, data):
+ fileName = config.plugins.birthdayreminder.file.value
+ try:
+ with open(fileName, "w") as f:
+ f.write(data)
+ except IOError as err:
+ (error_no, error_str) = err.args
+ print("[%s] ERROR writing to file %s. Error: %s, %s" % (MODULE_NAME, fileName, error_no, error_str))
+ text = _("Error writing file %s.\n\nError: %s, %s") % (fileName, error_no, error_str)
+ Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=3)
+
+ def loadStore(self): # read the birthday information from file
+ fileName = config.plugins.birthdayreminder.file.value
+ print("[%s] reading from file %s" % (MODULE_NAME, fileName))
+ tmpList = []
+ if isfile(fileName):
+ try:
+ with open(fileName, "rb") as f:
+ tmpList = load(f)
+ except IOError as err:
+ (error_no, error_str) = err.args
+ print("[%s] ERROR reading from file %s. Error: %s, %s" % (MODULE_NAME, fileName, error_no, error_str))
+ text = _("Error reading file %s.\n\nError: %s, %s") % (fileName, error_no, error_str)
+ Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=3)
+ print("[%s] read %s birthdays" % (MODULE_NAME, len(tmpList)))
+ else:
+ print("[%s] File %s not found." % (MODULE_NAME, fileName))
+ self.bDayList = tmpList
+
+ def saveStore(self, data=None): # write the birthday information to file
+ fileName = config.plugins.birthdayreminder.file.value
+ print("[%s] writing to file %s" % (MODULE_NAME, fileName))
+ try:
+ with open(fileName, "wb") as f:
+ dump(data if data else self.getBirthdayList(), f)
+ print("[%s] wrote %s birthdays to %s" % (MODULE_NAME, self.getSize(), fileName))
+ except IOError as err:
+ (error_no, error_str) = err.args
+ print("[%s] ERROR writing to file %s. Error: %s, %s" % (MODULE_NAME, fileName, error_no, error_str))
+ text = _("Error writing file %s.\n\nError: %s, %s") % (MODULE_NAME, fileName, error_no, error_str)
+ Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=3)
+
+ def getSize(self): # return the number of birthdays in list
+ return len(self.bDayList)
+
+ def getBirthdayList(self): # return the list of birthdays
+ return self.bDayList
+
+ def addEntry(self, entry): # add a new entry to the list
+ self.bDayList.append(entry)
+ self.saveStore()
+
+ def removeEntry(self, idx): # remove an entry from the list
+ self.bDayList.pop(idx)
+ self.saveStore()
+
+ def updateEntry(self, oldEntry, newEntry): # update a list entry
+ idx = self.bDayList.index(oldEntry)
+ self.bDayList[idx] = newEntry
+ self.saveStore()
+
+ def getEntry(self, idx): # get a list entry
+ return self.bDayList[idx]
+
+
+class BirthdayReminder(Screen, HelpableScreen):
+ skin = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {"template": [
+ MultiContentEntryText(pos = (0, 0), size = (295, 25), font=0, flags = RT_HALIGN_LEFT, text = 0),
+ MultiContentEntryText(pos = (300, 0), size = (165, 25), font=0, flags = RT_HALIGN_CENTER, text = 1),
+ MultiContentEntryText(pos = (470, 0), size = (50, 25), font=0, flags = RT_HALIGN_RIGHT, text = 2),
+ ],
+ "fonts": [gFont("Regular", 22)],
+ "itemHeight": 25
+ }
+
+
+ """ % _("Birthday Reminder")
+
+ def __init__(self, session, birthdaytimer):
+ self.session = session
+ self.birthdaytimer = birthdaytimer
+ Screen.__init__(self, session)
+ self["key_red"] = StaticText(_("Add"))
+ self["key_green"] = StaticText("")
+ self["key_yellow"] = StaticText("")
+ self["key_blue"] = StaticText(_("Extras"))
+ self["name"] = Label(_("Name"))
+ self["birthday"] = Label(_("Birthday"))
+ self["age"] = Label(_("Age"))
+ self["list"] = BirthdayList(self.birthdaytimer.getBirthdayList())
+ HelpableScreen.__init__(self)
+ self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions",
+ {
+ "cancel": (self.exit, _("Exit the plugin")),
+ }, -1)
+ self["BaseActions"] = HelpableActionMap(self, "ColorActions",
+ {
+ "red": (self.addBirthday, _("Add a birthday")),
+ "blue": (self.openExtras, _("Open the extras menu")),
+ }, -1)
+ # this ActionMap can be enabled/disabled depending on the number of list entries
+ self["EditActions"] = HelpableActionMap(self, "ColorActions",
+ {
+ "green": (self.editBirthday, _("Edit the selected entry")),
+ "yellow": (self.removeBirthday, _("Remove the selected entry")),
+ }, -1)
+ self["ChannelSelectBaseActions"] = HelpableActionMap(self, "ChannelSelectBaseActions",
+ {
+ "prevBouquet": (self.changeSortingUp, _("Sorting next")),
+ "nextBouquet": (self.changeSortingDown, _("Sorting previous")),
+ }, -1)
+ self.setButtonState()
+ self.onLayoutFinish.append(self.cbOnLayoutFinished)
+
+ def cbOnLayoutFinished(self):
+ self.setListSorted()
+
+ def exit(self): # exit the plugin
+ self.close()
+
+ def addBirthday(self): # add a birthday
+ self.session.openWithCallback(self.cbAddBirthday, EditBirthdaySetting)
+
+ def editBirthday(self): # edit a birthday
+ selected = self["list"].getCurrent()
+ t = strptime(selected[1], "%m/%d/%Y") if config.plugins.birthdayreminder.dateFormat.value == "mmddyyyy" else strptime(selected[1], "%d.%m.%Y")
+ newDate = date(*t[:3])
+ self.bDayBeforeChange = (selected[0], newDate)
+ self.session.openWithCallback(self.cbEditBirthday, EditBirthdaySetting, self.bDayBeforeChange)
+
+ def removeBirthday(self): # remove a birthday?
+ selected = self["list"].getCurrent()
+ self.session.openWithCallback(self.cbDeleteBirthday, MessageBox, _("Do you really want to delete the entry for %s?") % selected[0], MessageBox.TYPE_YESNO)
+
+ def setButtonState(self): # set the state of the buttons depending on the list size
+ if not self.birthdaytimer.getSize(): # no entries in list
+ self["EditActions"].setEnabled(False)
+ self["key_green"].setText("")
+ self["key_yellow"].setText("")
+ else:
+ self["EditActions"].setEnabled(True)
+ self["key_green"].setText(_("Edit"))
+ self["key_yellow"].setText(_("Remove"))
+
+ def openExtras(self): # open extras menu
+ choiceList = [(_("Export CSV file"), "csvexport"), (_("Import CSV file"), "csvimport"), (_("Distribute birthdays to other Dreamboxes"), "sendListOffer")]
+ self.session.openWithCallback(self.cbOpenExtras, ChoiceBox, title=_("What do you want to do?"), list=choiceList)
+
+ def cbOpenExtras(self, result):
+ if result is None:
+ return
+ elif result[1] == "csvexport":
+ self.saveCSV()
+ elif result[1] == "csvimport":
+ self.loadCSV()
+ elif result[1] == "sendListOffer":
+ self.sendListOffer()
+
+ def cbAddBirthday(self, name, birthday): # this callback is called when a birthday was added
+ if name is None and birthday is None:
+ return
+ entry = (name, birthday)
+ self.birthdaytimer.addEntry(entry)
+ self["list"].setList(self.birthdaytimer.getBirthdayList())
+ self.setButtonState()
+ self.birthdaytimer.addTimer(entry)
+ if config.plugins.birthdayreminder.preremind.value != "-1":
+ self.birthdaytimer.addTimer(entry, preremind=True)
+ self.setListSorted(newEntry=entry)
+
+ def cbEditBirthday(self, name, birthday): # this callback is called when a birthday was edited
+ (oldName, oldBirthday) = self.bDayBeforeChange
+ if (name == oldName and birthday == oldBirthday) or (name is None and birthday is None):
+ return
+ newEntry = (name, birthday)
+ self.birthdaytimer.updateEntry(self.bDayBeforeChange, newEntry)
+ self.birthdaytimer.updateTimer(self.bDayBeforeChange, newEntry)
+ self["list"].updateList(self.birthdaytimer.getBirthdayList())
+ self.setListSorted(newEntry=newEntry)
+
+ def cbDeleteBirthday(self, result): # really delete the selected birthday entry?
+ if not result:
+ return
+ selected = self["list"].getCurrent()
+ t = strptime(selected[1], "%m/%d/%Y") if config.plugins.birthdayreminder.dateFormat.value == "mmddyyyy" else strptime(selected[1], "%d.%m.%Y")
+ newDate = date(*t[:3])
+ entry = (selected[0], newDate)
+ self.birthdaytimer.removeTimersForEntry(entry)
+ idx = self["list"].getIndex()
+ self.birthdaytimer.removeEntry(idx)
+ self["list"].setList(self.birthdaytimer.getBirthdayList())
+ size = self.birthdaytimer.getSize() # set selection
+ if size > 1 and idx < size:
+ self["list"].setIndex(idx)
+ elif size > 1 and idx >= size:
+ self["list"].setIndex(size - 1)
+ self.setButtonState()
+
+ def changeSortingUp(self): # change direction of sorting upwards
+ if config.plugins.birthdayreminder.sortby.value == "1":
+ config.plugins.birthdayreminder.sortby.value = "3"
+ else:
+ val = int(config.plugins.birthdayreminder.sortby.value) - 1
+ config.plugins.birthdayreminder.sortby.value = str(val)
+ config.plugins.birthdayreminder.sortby.save()
+ self.setListSorted()
+
+ def changeSortingDown(self): # change direction of sorting downwards
+ if config.plugins.birthdayreminder.sortby.value == "3":
+ config.plugins.birthdayreminder.sortby.value = "1"
+ else:
+ val = int(config.plugins.birthdayreminder.sortby.value) + 1
+ config.plugins.birthdayreminder.sortby.value = str(val)
+ config.plugins.birthdayreminder.sortby.save()
+ self.setListSorted()
+
+ def setListSorted(self, newEntry=None): # birthday list sorting
+ if not self.birthdaytimer.getSize():
+ return
+ if config.plugins.birthdayreminder.sortby.value == "1": # sort by name
+ self["name"].instance.setForegroundColor(parseColor("yellow"))
+ self["birthday"].instance.setForegroundColor(parseColor("white"))
+ self["age"].instance.setForegroundColor(parseColor("white"))
+ self.birthdaytimer.bDayList.sort(key=lambda t: tuple(t[0].lower()))
+ elif config.plugins.birthdayreminder.sortby.value == "2": # sort by upcoming birthday
+ self["name"].instance.setForegroundColor(parseColor("white"))
+ self["birthday"].instance.setForegroundColor(parseColor("yellow"))
+ self["age"].instance.setForegroundColor(parseColor("white"))
+ self.birthdaytimer.bDayList.sort(key=cmp_to_key(self.compareDates))
+ else: # sort by age
+ self["name"].instance.setForegroundColor(parseColor("white"))
+ self["birthday"].instance.setForegroundColor(parseColor("white"))
+ self["age"].instance.setForegroundColor(parseColor("yellow"))
+ self.birthdaytimer.bDayList.sort(key=cmp_to_key(self.compareAges))
+ self["list"].setList(self.birthdaytimer.getBirthdayList())
+ if newEntry:
+ newIdx = self["list"].getIndexForEntry(newEntry)
+ self["list"].setIndex(newIdx)
+
+ def compareDates(self, x, y):
+ x = x[1]
+ y = y[1]
+ today = date.today()
+ try:
+ bDay1 = x.replace(year=today.year)
+ except ValueError: # raised on feb 29th
+ bDay1 = x.replace(year=today.year, day=x.day - 1)
+ if bDay1 < today: # next birthday in next year
+ try:
+ bDay1 = x.replace(year=today.year + 1)
+ except ValueError: # raised on feb 29th
+ bDay1 = x.replace(year=today.year + 1, day=x.day - 1)
+ ts1 = int(mktime(bDay1.timetuple()))
+ try:
+ bDay2 = y.replace(year=today.year)
+ except ValueError: # raised on feb 29th
+ bDay2 = y.replace(year=today.year, day=y.day - 1)
+ if bDay2 < today: # next birthday in next year
+ try:
+ bDay2 = y.replace(year=today.year + 1)
+ except ValueError: # raised on feb 29th
+ bDay2 = y.replace(year=today.year + 1, day=y.day - 1)
+ ts2 = int(mktime(bDay2.timetuple()))
+ return ts1 - ts2
+
+ def compareAges(self, x, y):
+ x = x[1]
+ y = y[1]
+ ageX = getAge(x)
+ ageY = getAge(y)
+ if ageX == ageY: # ages are the same, sort by birthday
+ tX = int(mktime(x.timetuple()))
+ tY = int(mktime(y.timetuple()))
+ return tX - tY
+ else:
+ return ageX - ageY
+
+ def saveCSV(self):
+ print("[%s] exporting CSV file %s" % (MODULE_NAME, CSVFILE))
+ try:
+ with open(CSVFILE, "w") as csvFile:
+ writer = csv_writer(csvFile)
+ writer.writerows(self.birthdaytimer.getBirthdayList())
+ self.session.open(MessageBox, _("Wrote CSV file %s.") % CSVFILE, MessageBox.TYPE_INFO, timeout=3)
+ except Exception as err:
+ self.session.open(MessageBox, _("Can't write CSV file '%s'. Error: %s") % (CSVFILE, str(err)), MessageBox.TYPE_ERROR, timeout=3)
+
+ def loadCSV(self):
+ print("[%s] importing CSV file %s" % (MODULE_NAME, CSVFILE))
+ if not isfile(CSVFILE):
+ text = _("Can't find CSV file %s!") % CSVFILE
+ self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=3)
+ return
+ csvList = []
+ try:
+ with open(CSVFILE, "r") as csvFile:
+ reader = csv_reader(csvFile)
+ for row in reader:
+ name = row[0]
+ bDay = row[1]
+ if bDay[:4].isdigit() and int(bDay[:4]) < 1910: # avoid crashes due to E2-problem with mktime() and dates < 1901-12-15'
+ bDay = "1910%s" % bDay[4:]
+ newDate = date.fromtimestamp(mktime(strptime(bDay, "%Y-%m-%d")))
+ entry = (name, newDate)
+ csvList.append(entry)
+ except IOError as err:
+ (error_no, error_str) = err.args
+ text = _("Error reading file %s.\n\nError: %s, %s") % (CSVFILE, error_no, error_str)
+ self.session.open(MessageBox, text, MessageBox.TYPE_ERROR, timeout=3)
+ return
+ self.birthdaytimer.bDayList = copy(csvList) # the critical part is done, now update the lists and timers
+ self.birthdaytimer.saveStore()
+ self["list"].setList(self.birthdaytimer.getBirthdayList())
+ self.setListSorted()
+ self.setButtonState()
+ self.birthdaytimer.timer_list = []
+ self.birthdaytimer.start()
+ self.session.open(MessageBox, _("CSV import successful!"), MessageBox.TYPE_INFO, timeout=3)
+
+ def sendListOffer(self):
+ print("[%s] broadcasting list offer" % MODULE_NAME)
+ self.birthdaytimer.broadcastProtocol.sendBroadcast("offeringList")
+
+
+class BirthdayList(List):
+ def __init__(self, list=None):
+ List.__init__(self, list=[])
+ self.__list = list
+
+ def setList(self, list):
+ self.__list = list
+ self.changed((self.CHANGED_ALL,))
+
+ def getList(self): # some kind of buildFunc replacement :)
+ dformat = "%m/%d/%Y" if config.plugins.birthdayreminder.dateFormat.value == "mmddyyyy" else "%d.%m.%Y"
+ l = []
+ if self.__list:
+ for entry in self.__list:
+ name = entry[0]
+ birthday = entry[1].strftime(dformat)
+ age = str(getAge(entry[1]))
+ l.append((name, birthday, age))
+ return l
+ list = property(getList, setList)
+
+ def getIndexForEntry(self, entry):
+ if self.__list:
+ return self.__list.index(entry) if self.master is not None else None
+
+
+class EditBirthdaySetting(Setup):
+ def __init__(self, session, entry=None):
+ (name, birthday) = entry if entry is not None else ("", date(*strptime("1.1.1900", "%d.%m.%Y")[:3]))
+ config.plugins.birthdayreminder.name = NoSave(ConfigText(default=name, fixed_size=False, visible_width=40))
+ config.plugins.birthdayreminder.day = NoSave(ConfigInteger(default=birthday.day, limits=(1, 31)))
+ config.plugins.birthdayreminder.month = NoSave(ConfigInteger(default=birthday.month, limits=(1, 12)))
+ config.plugins.birthdayreminder.year = NoSave(ConfigInteger(default=birthday.year, limits=(1900, 2050)))
+ Setup.__init__(self, session, "EditBirthdaySetting", plugin="Extensions/BirthdayReminder", PluginLanguageDomain="BirthdayReminder")
+ self.setTitle(_("Add birthday") if entry is None else _("Edit birthday"))
+
+ def keyCancel(self):
+ self.close(None, None)
+
+ def keySave(self):
+ try:
+ birthdayDt = datetime(config.plugins.birthdayreminder.year.value, config.plugins.birthdayreminder.month.value, config.plugins.birthdayreminder.day.value)
+ birthday = datetime.date(birthdayDt)
+ self.close(config.plugins.birthdayreminder.name.value, birthday)
+ except ValueError:
+ self["footnote"].setText(_("Invalid date!"))
+
+
+class BirthdayReminderSettings(Setup):
+ def __init__(self, session, birthdaytimer):
+ self.birthdaytimer = birthdaytimer
+ path, filename = split(config.plugins.birthdayreminder.file.value)
+ self.path = NoSave(ConfigDirectory(default=path))
+ self.filename = NoSave(ConfigText(default=filename, visible_width=50, fixed_size=False))
+ self.preremind = config.plugins.birthdayreminder.preremind.value
+ self.notificationTime = copy(config.plugins.birthdayreminder.notificationTime.value)
+ Setup.__init__(self, session, "BirthdayReminderSettings", plugin="Extensions/BirthdayReminder", PluginLanguageDomain="BirthdayReminder")
+ self.setTitle(_("Birthday Reminder Settings"))
+ self["key_blue"] = StaticText(_("Birthdays"))
+ self["blueActions"] = HelpableActionMap(self, ["ColorActions"], {
+ "blue": (self.editBirthdays, _("Edit birthdays"))
+ }, prio=0)
+
+ def keySelect(self):
+ if self.getCurrentItem() == self.path:
+ self.session.openWithCallback(self.keySelectCallback, BirthdayReminderLocationBox, initDir=self.path.value)
+ return
+ Setup.keySelect(self)
+
+ def keySelectCallback(self, path):
+ if path is not None:
+ path = join(path, "")
+ self.path.value = path
+ self["config"].invalidateCurrent()
+ self.changedEntry()
+
+ def keySave(self):
+ config.plugins.birthdayreminder.file.value = join(self.path.value, self.filename.value)
+ config.plugins.birthdayreminder.file.save()
+ config.plugins.birthdayreminder.file.changed()
+ if config.plugins.birthdayreminder.preremind.value != self.preremind:
+ if self.preremind == "-1":
+ config.plugins.birthdayreminder.preremindChanged.setValue(True) # there are no preremind timers, add new timers
+ else:
+ config.plugins.birthdayreminder.preremindChanged.setValue(False) # change existing preremind timers
+ if config.plugins.birthdayreminder.notificationTime.value != self.notificationTime:
+ config.plugins.birthdayreminder.notificationTimeChanged.setValue(True)
+ Setup.keySave(self)
+
+ def editBirthdays(self):
+ self.session.open(BirthdayReminder, self.birthdaytimer)
+
+
+class BirthdayReminderLocationBox(LocationBox):
+ def __init__(self, session, initDir):
+ inhibit = defaultInhibitDirs
+ inhibit.remove("/etc")
+ LocationBox.__init__(self, session, text=_("Select a path for the birthday file"), currDir=join(initDir, ""), inhibitDirs=inhibit,)
+ self.skinName = ["WeatherSettingsLocationBox", "LocationBox"]
+
+
+def getAge(birthday):
+ today = date.today()
+ try:
+ bDay = birthday.replace(year=today.year) # take care of feb 29th, use feb 28th if necessary
+ except ValueError: # raised on feb 29th
+ bDay = birthday.replace(year=today.year, day=birthday.day - 1)
+ age = today.year - birthday.year
+ return age - 1 if bDay > today else age
diff --git a/birthdayreminder/src/BirthdayTimer.py b/birthdayreminder/src/BirthdayTimer.py
new file mode 100644
index 000000000..01fed03b9
--- /dev/null
+++ b/birthdayreminder/src/BirthdayTimer.py
@@ -0,0 +1,252 @@
+# OWN IMPORTS
+from .BirthdayNetworking import BroadcastProtocol, TransferServerFactory, TransferClientFactory
+from .BirthdayReminder import BirthdayStore, getAge
+
+# PYTHON IMPORTS
+from datetime import datetime, date, timedelta, time as dt_time
+from time import mktime, time, strftime, strptime
+from timer import Timer, TimerEntry
+from twisted.internet import reactor
+
+# ENIGMA IMPORTS
+from Components.config import config
+from enigma import eDVBLocalTimeHandler
+from Screens.MessageBox import MessageBox
+from Tools import Notifications
+
+# for localized messages
+from . import _
+
+
+class BirthdayTimerEntry(TimerEntry):
+ def __init__(self, begin, end, preremind):
+ TimerEntry.__init__(self, int(begin), int(end))
+ self.preremind = preremind
+ self.state = self.StatePrepared
+
+ def getNextActivation(self):
+ return self.begin
+
+ def activate(self):
+ if self.preremind:
+ when = config.plugins.birthdayreminder.preremind.getText()
+ print("[Birthday Reminder] %s will turn %s in %s!" % (self.bDay[0], getAge(self.bDay[1]) + 1, when))
+ text = _("%s will turn %s in %s!") % (self.bDay[0], getAge(self.bDay[1]) + 1, when)
+ else:
+ print("[Birthday Reminder] It's %s's birthday today!" % self.bDay[0])
+
+ if config.plugins.birthdayreminder.dateFormat.value == "mmddyyyy":
+ format = "%m/%d/%Y"
+ else:
+ format = "%d.%m.%Y"
+ birthday = self.bDay[1].strftime(format)
+
+ text = _("Today is %s's birthday!\n\nShe/he was born on %s and is now %s year(s) old.") % (self.bDay[0], birthday, getAge(self.bDay[1]))
+
+ Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO)
+
+ # activate the timer for next year
+ now = date.today()
+ bDay = self.bDay[1]
+
+ # set timer to feb 28th for birthdays on feb 29th
+ try:
+ bDayNextYear = date(now.year + 1, bDay.month, bDay.day)
+ except ValueError: # raised on feb 29th
+ bDayNextYear = date(now.year + 1, bDay.month, bDay.day - 1)
+
+ self.begin = int(mktime(bDayNextYear.timetuple()))
+ self.end = self.begin - 1
+ self.state = self.StatePrepared
+
+ return True
+
+ def shouldSkip(self):
+ return False
+
+ def timeChanged(self):
+ self.state = self.StatePrepared
+
+
+class BirthdayTimer(Timer, BirthdayStore):
+ def __init__(self):
+ BirthdayStore.__init__(self)
+ Timer.__init__(self)
+
+ # this is used to detect settings changes, because we only want to change preremind timers if a different value was saved by the user
+ config.plugins.birthdayreminder.preremindChanged.addNotifier(self.cbPreremindChanged, initial_call=False)
+ config.plugins.birthdayreminder.notificationTimeChanged.addNotifier(self.cbNotificationTimeChanged, initial_call=False)
+
+ # let's wait for the system time being up to date before starting the timers. needed when the box was powered off
+ if not eDVBLocalTimeHandler.getInstance().ready():
+ eDVBLocalTimeHandler.getInstance().m_timeUpdated.get().append(self.startTimer)
+ else:
+ self.start()
+ self.startNetworking()
+ self.broadcastPort = None
+ self.transferServerPort = None
+
+ def startTimer(self):
+ eDVBLocalTimeHandler.getInstance().m_timeUpdated.get().remove(self.startTimer)
+ self.start()
+ self.startNetworking()
+
+ def start(self):
+ if not self.getSize():
+ print("[Birthday Reminder] Got no birthdays, no timers to add.")
+ return
+
+ self.addAllTimers()
+
+ def stop(self):
+ self.stopNetworking()
+
+ print("[Birthday Reminder] stopping timer...")
+
+ self.timer.stop()
+ self.timer_list = []
+ self.timer.callback.remove(self.calcNextActivation)
+ self.timer = None
+
+ config.plugins.birthdayreminder.preremindChanged.notifiers.remove(self.cbPreremindChanged)
+ config.plugins.birthdayreminder.notificationTimeChanged.notifiers.remove(self.cbNotificationTimeChanged)
+
+ def startNetworking(self):
+ print("[Birthday Reminder] starting network communication...")
+
+ port = config.plugins.birthdayreminder.broadcastPort.value
+ self.transferServerProtocol = TransferServerFactory(self)
+ self.broadcastProtocol = BroadcastProtocol(self)
+ try:
+ self.transferServerPort = reactor.listenTCP(port, self.transferServerProtocol)
+ self.broadcastPort = reactor.listenUDP(port, self.broadcastProtocol)
+ except:
+ print("[Birthday Reminder] can't listen on port %s" % port)
+
+ def stopNetworking(self):
+ print("[Birthday Reminder] stopping network communication...")
+ self.broadcastPort and self.broadcastPort.stopListening()
+ self.transferServerPort and self.transferServerPort.stopListening()
+
+ def requestBirthdayList(self, addr):
+ print("[Birthday Reminder] requesting birthday list from %s" % addr[0])
+ reactor.connectTCP(addr[0], 7374, TransferClientFactory(self, "requestingList"))
+
+ def sendPingResponse(self, addr):
+ print("[Birthday Reminder] sending ping response to %s" % addr[0])
+ reactor.connectTCP(addr[0], 7374, TransferClientFactory(self, "pong"))
+
+ def updateTimer(self, oldBirthday, newBirthday):
+ print("[Birthday Reminder] updating timer for %s" % oldBirthday[0])
+
+ self.removeTimersForEntry(oldBirthday)
+ self.addTimer(newBirthday)
+
+ # add a preremind timer also?
+ if config.plugins.birthdayreminder.preremind.getValue() != "-1":
+ self.addTimer(newBirthday, preremind=True)
+
+ def addTimer(self, entry, preremind=False):
+ if preremind:
+ print("[Birthday Reminder] Adding preremind timer for %s" % entry[0])
+ else:
+ print("[Birthday Reminder] Adding birthday timer for %s" % entry[0])
+
+ timeList = config.plugins.birthdayreminder.notificationTime.value
+ notifyTime = dt_time(timeList[0], timeList[1])
+ now = date.today()
+ bDay = entry[1]
+
+ if preremind:
+ numDays = int(config.plugins.birthdayreminder.preremind.getValue())
+ # set timer to feb 28th for birthdays on feb 29th
+ try:
+ dateThisYear = date(now.year, bDay.month, bDay.day) - timedelta(numDays)
+ except ValueError: # raised on feb 29th
+ dateThisYear = date(now.year, bDay.month, bDay.day - 1) - timedelta(numDays)
+ else:
+ # set timer to feb 28th for birthdays on feb 29th
+ try:
+ dateThisYear = date(now.year, bDay.month, bDay.day)
+ except ValueError: # raised on feb 29th
+ dateThisYear = date(now.year, bDay.month, bDay.day - 1)
+
+ dateTimeThisYear = datetime.combine(dateThisYear, notifyTime)
+
+ if dateThisYear >= now: # check if the birthday is in this year
+ begin = int(mktime(dateTimeThisYear.timetuple()))
+ else: # birthday is in the past, we need a timer for the next year
+ # set timer to feb 28th for birthdays on feb 29th
+ try:
+ bDayNextYear = dateTimeThisYear.replace(year=dateThisYear.year + 1)
+ except ValueError: # raised on feb 29th
+ bDayNextYear = dateTimeThisYear.replace(year=dateThisYear.year + 1, day=dateThisYear.day - 1)
+
+ begin = int(mktime(bDayNextYear.timetuple()))
+
+ end = begin - 1
+ timerEntry = BirthdayTimerEntry(begin, end, preremind)
+ timerEntry.bDay = entry
+ self.addTimerEntry(timerEntry)
+
+ def removeTimersForEntry(self, entry):
+ for timer in self.timer_list[:]:
+ if timer.bDay == entry:
+ if timer.preremind:
+ print("[Birthday Reminder] Removing preremind timer for %s" % entry[0])
+ else:
+ print("[Birthday Reminder] Removing birthday timer for %s" % entry[0])
+ self.timer_list.remove(timer)
+
+ self.calcNextActivation()
+
+ def removePreremindTimers(self):
+ print("[Birthday Reminder] Removing all preremind timers...")
+ for timer in self.timer_list[:]:
+ if timer.preremind:
+ self.timer_list.remove(timer)
+
+ self.calcNextActivation()
+
+ def addAllTimers(self):
+ print("[Birthday Reminder] Adding timers for all birthdays...")
+ bDayList = self.getBirthdayList()
+ for entry in bDayList:
+ self.addTimer(entry)
+
+ # add a preremind timer also?
+ if config.plugins.birthdayreminder.preremind.getValue() != "-1":
+ self.addTimer(entry, preremind=True)
+
+ def showReceivedMessage(self, numReceived, peer):
+ text = _("Birthday Reminder received %s birthdays from %s.") % (numReceived, peer)
+ Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO)
+
+ def cbPreremindChanged(self, configElement=None):
+ if config.plugins.birthdayreminder.preremind.value == "-1": # remove all preremind timers
+ self.removePreremindTimers()
+ else: # we need to add or change timers
+ if config.plugins.birthdayreminder.preremindChanged.value: # there are no preremind timers, add new timers
+ print("[Birthday Reminder] Adding new preremind timers...")
+ for timer in self.timer_list[:]:
+ self.addTimer(timer.bDay, preremind=True)
+ else: # change existing preremind timers
+ print("[Birthday Reminder] Changing date of preremind timers...")
+ self.removePreremindTimers()
+
+ for timer in self.timer_list[:]:
+ self.addTimer(timer.bDay, preremind=True)
+
+ def cbNotificationTimeChanged(self, configElement=None):
+ print("[Birthday Reminder] Changing timer times...")
+
+ timeList = config.plugins.birthdayreminder.notificationTime.value
+ notifyTime = dt_time(timeList[0], timeList[1])
+
+ for timer in self.timer_list:
+ day = date.fromtimestamp(timer.begin)
+ newDateTime = datetime.combine(day, notifyTime)
+ timer.begin = int(mktime(newDateTime.timetuple()))
+ timer.end = timer.begin - 1
+
+ self.calcNextActivation()
diff --git a/birthdayreminder/src/LICENSE b/birthdayreminder/src/LICENSE
new file mode 100644
index 000000000..8038f9570
--- /dev/null
+++ b/birthdayreminder/src/LICENSE
@@ -0,0 +1,14 @@
+All Files of this Software are licensed under the Creative Commons
+Attribution-NonCommercial-ShareAlike 3.0 Unported
+License if not stated otherwise in a Files Head. To view a copy of this license, visit
+http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative
+Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
+
+Additionally, this plugin may only be distributed and executed on hardware which
+is licensed by Dream Multimedia GmbH.
+
+This plugin is NOT free software. It is open source, you are allowed to
+modify it (if you keep the license), but it may not be commercially
+distributed other than under the conditions noted above.
+This applies to the source code as a whole as well as to parts of it, unless
+explicitely stated otherwise.
diff --git a/birthdayreminder/src/Makefile.am b/birthdayreminder/src/Makefile.am
new file mode 100644
index 000000000..222c7486f
--- /dev/null
+++ b/birthdayreminder/src/Makefile.am
@@ -0,0 +1,6 @@
+installdir = $(libdir)/enigma2/python/Plugins/Extensions/BirthdayReminder
+
+install_PYTHON = *.py
+
+install_DATA = maintainer.info LICENSE plugin.png setup.xml
+
diff --git a/birthdayreminder/src/__init__.py b/birthdayreminder/src/__init__.py
new file mode 100644
index 000000000..f4a5cd707
--- /dev/null
+++ b/birthdayreminder/src/__init__.py
@@ -0,0 +1,22 @@
+from Components.Language import language
+from Tools.Directories import resolveFilename, SCOPE_PLUGINS
+import gettext
+
+PluginLanguageDomain = "BirthdayReminder"
+PluginLanguagePath = "Extensions/BirthdayReminder/locale"
+
+
+def localeInit():
+ gettext.bindtextdomain(PluginLanguageDomain, resolveFilename(SCOPE_PLUGINS, PluginLanguagePath))
+
+
+def _(txt):
+ if gettext.dgettext(PluginLanguageDomain, txt):
+ return gettext.dgettext(PluginLanguageDomain, txt)
+ else:
+ print("[%s] fallback to default translation for %s" % (PluginLanguageDomain, txt))
+ return gettext.gettext(txt)
+
+
+localeInit()
+language.addCallback(localeInit)
diff --git a/birthdayreminder/src/maintainer.info b/birthdayreminder/src/maintainer.info
new file mode 100644
index 000000000..4ecc4cf8b
--- /dev/null
+++ b/birthdayreminder/src/maintainer.info
@@ -0,0 +1,2 @@
+shaderman@dreambox-tools.info
+BirthdayReminder
diff --git a/birthdayreminder/src/plugin.png b/birthdayreminder/src/plugin.png
new file mode 100644
index 000000000..9ea49ff69
Binary files /dev/null and b/birthdayreminder/src/plugin.png differ
diff --git a/birthdayreminder/src/plugin.py b/birthdayreminder/src/plugin.py
new file mode 100644
index 000000000..b0f39e779
--- /dev/null
+++ b/birthdayreminder/src/plugin.py
@@ -0,0 +1,78 @@
+#
+# Birthday Reminder E2 Plugin
+#
+# $Id: plugin.py,v 1.0 2011-08-29 00:00:00 Shaderman Exp $
+#
+# Coded by Shaderman (c) 2011
+# plugin.png by Sakartvelo with images from BazaarDesigns.com
+# Support: www.dreambox-tools.info
+#
+# This plugin is licensed under the Creative Commons
+# Attribution-NonCommercial-ShareAlike 3.0 Unported
+# License. To view a copy of this license, visit
+# http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative
+# Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
+#
+# Alternatively, this plugin may be distributed and executed on hardware which
+# is licensed by Dream Multimedia GmbH.
+
+# This plugin is NOT free software. It is open source, you are allowed to
+# modify it (if you keep the license), but it may not be commercially
+# distributed other than under the conditions noted above.
+#
+
+# Python 3 port and cleanup by jbleyel (c) 2022
+
+# OWN IMPORTS
+from .BirthdayReminder import BirthdayReminder, BirthdayReminderSettings
+from .BirthdayTimer import BirthdayTimer
+
+# ENIGMA IMPORTS
+from Components.config import config, ConfigSubsection, ConfigText, ConfigSelection, ConfigYesNo, NoSave, ConfigClock, ConfigInteger
+from Plugins.Plugin import PluginDescriptor
+
+# for localized messages
+from . import _
+
+
+config.plugins.birthdayreminder = ConfigSubsection()
+config.plugins.birthdayreminder.file = ConfigText(default="/etc/enigma2/birthdayreminder")
+config.plugins.birthdayreminder.dateFormat = ConfigSelection(default="ddmmyyyy", choices=[("ddmmyyyy", "DD.MM.YYYY"), ("mmddyyyy", "MM/DD/YYYY")])
+config.plugins.birthdayreminder.broadcasts = ConfigYesNo(default=True)
+config.plugins.birthdayreminder.preremind = ConfigSelection(default="7", choices=[("-1", _("Disabled")), ("1", _("1 day")), ("3", _("3 days")), ("7", _("1 week"))])
+config.plugins.birthdayreminder.preremindChanged = NoSave(ConfigYesNo(default=False))
+config.plugins.birthdayreminder.notificationTime = ConfigClock(default=64800) # 19:00
+config.plugins.birthdayreminder.notificationTimeChanged = NoSave(ConfigYesNo(default=False))
+config.plugins.birthdayreminder.sortby = ConfigSelection(default="1", choices=[
+ ("1", _("Name")),
+ ("2", _("Next birthday")),
+ ("3", _("Age"))
+ ])
+config.plugins.birthdayreminder.showInExtensions = ConfigYesNo(default=False)
+config.plugins.birthdayreminder.broadcastPort = ConfigInteger(default=7374, limits=(1024, 49151))
+
+
+birthdaytimer = BirthdayTimer()
+
+
+def settings(session, **kwargs):
+ session.open(BirthdayReminderSettings, birthdaytimer)
+
+
+def autostart(reason, **kwargs):
+ if reason == 1:
+ birthdaytimer.stop()
+
+
+def main(session, **kwargs):
+ session.open(BirthdayReminder, birthdaytimer)
+
+
+def Plugins(**kwargs):
+ pluginList = [
+ PluginDescriptor(where=[PluginDescriptor.WHERE_AUTOSTART, PluginDescriptor.WHERE_SESSIONSTART], fnc=autostart),
+ PluginDescriptor(name="Birthday Reminder", description=_("Helps to remind of birthdays"), where=PluginDescriptor.WHERE_PLUGINMENU, icon="plugin.png", fnc=settings)
+ ]
+ if config.plugins.birthdayreminder.showInExtensions.value:
+ pluginList.append(PluginDescriptor(name="Birthday Reminder", description=_("Helps to remind of birthdays"), where=[PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_EVENTINFO], fnc=main))
+ return pluginList
diff --git a/birthdayreminder/src/setup.xml b/birthdayreminder/src/setup.xml
new file mode 100644
index 000000000..7741ea934
--- /dev/null
+++ b/birthdayreminder/src/setup.xml
@@ -0,0 +1,23 @@
+
+
+ - self.path
+ - self.filename
+ - config.plugins.birthdayreminder.dateFormat
+ - config.plugins.birthdayreminder.preremind
+ - config.plugins.birthdayreminder.notificationTime
+ - config.plugins.birthdayreminder.sortby
+ - config.plugins.birthdayreminder.showInExtensions
+ - config.plugins.birthdayreminder.broadcastPort
+
+
+ - config.plugins.birthdayreminder.name
+
+ - config.plugins.birthdayreminder.month
+ - config.plugins.birthdayreminder.day
+
+ - config.plugins.birthdayreminder.day
+ - config.plugins.birthdayreminder.month
+
+ - config.plugins.birthdayreminder.year
+
+
\ No newline at end of file
diff --git a/configure.ac b/configure.ac
index e019859bc..a54be40b9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -87,6 +87,11 @@ babelzapper/etc/Makefile
babelzapper/meta/Makefile
babelzapper/src/Makefile
+birthdayreminder/Makefile
+birthdayreminder/meta/Makefile
+birthdayreminder/po/Makefile
+birthdayreminder/src/Makefile
+
blindscan/src/Makefile
blindscan/po/Makefile
blindscan/Makefile