From f16c7c987a4a49f52af6e132176b2250b5663514 Mon Sep 17 00:00:00 2001 From: Christian Moser Date: Wed, 5 Mar 2025 10:23:21 +0100 Subject: [PATCH] added gettext support --- .gitignore | 3 + Makefile | 11 ++ PO/LINGUAS | 1 + PO/SOURCES | 30 +++++ PO/UI.SOURCES | 1 + PO/de.po | 199 +++++++++++++++++++++++++++++++ msys-install.sh | 13 ++ scripts/extract_localestrings.sh | 24 ++++ scripts/make_translations.sh | 21 ++++ scripts/update_po.sh | 18 +++ setup.py | 5 + sgbackup/gui/_app.py | 140 ++++++++++++---------- sgbackup/gui/_steam.py | 30 ++++- sgbackup/gui/appmenu.ui | 2 +- sgbackup/i18n.py | 30 +++++ sgbackup/locale/__init__.py | 1 + 16 files changed, 465 insertions(+), 64 deletions(-) create mode 100644 Makefile create mode 100644 PO/LINGUAS create mode 100644 PO/SOURCES create mode 100644 PO/UI.SOURCES create mode 100644 PO/de.po create mode 100755 scripts/extract_localestrings.sh create mode 100755 scripts/make_translations.sh create mode 100755 scripts/update_po.sh create mode 100644 sgbackup/i18n.py create mode 100644 sgbackup/locale/__init__.py diff --git a/.gitignore b/.gitignore index 9a3a5e1..05c77f2 100644 --- a/.gitignore +++ b/.gitignore @@ -185,3 +185,6 @@ cython_debug/ *.temp apidoc/ + +sgbackup/locale/*/*.po +sgbackup/locale/*/*.mo diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88eff2c --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +PO/messages.pot: + scripts/extract_loclestrings.sh + +pot: + scripts/extract_localestrings.sh + +update-po: PO/messages.pot + scripts/update_po.sh + +translations: + scripts/make_translations.sh diff --git a/PO/LINGUAS b/PO/LINGUAS new file mode 100644 index 0000000..c42e816 --- /dev/null +++ b/PO/LINGUAS @@ -0,0 +1 @@ +de \ No newline at end of file diff --git a/PO/SOURCES b/PO/SOURCES new file mode 100644 index 0000000..0b875d5 --- /dev/null +++ b/PO/SOURCES @@ -0,0 +1,30 @@ +sgbackup/archiver/tarfilearchiver.py +sgbackup/archiver/zipfilearchiver.py +sgbackup/archiver/_archiver.py +sgbackup/archiver/__init__.py +sgbackup/command.py +sgbackup/commands/help.py +sgbackup/commands/__init__.py +sgbackup/curses/__init__.py +sgbackup/curses/__main__.py +sgbackup/game.py +sgbackup/gui/_app.py +sgbackup/gui/_backupdialog.py +sgbackup/gui/_dialogs.py +sgbackup/gui/_gamedialog.py +sgbackup/gui/_settingsdialog.py +sgbackup/gui/_steam.py +sgbackup/gui/__init__.py +sgbackup/gui/__main__.py +sgbackup/help/__init__.py +sgbackup/i18n.py +sgbackup/locale/__init__.py +sgbackup/main.py +sgbackup/settings.py +sgbackup/steam.py +sgbackup/utility.py +sgbackup/version.py +sgbackup/_import_gtk.py +sgbackup/_logging.py +sgbackup/__init__.py +sgbackup/__main__.py diff --git a/PO/UI.SOURCES b/PO/UI.SOURCES new file mode 100644 index 0000000..88c41c8 --- /dev/null +++ b/PO/UI.SOURCES @@ -0,0 +1 @@ +sgbackup/gui/appmenu.ui diff --git a/PO/de.po b/PO/de.po new file mode 100644 index 0000000..2ceeb28 --- /dev/null +++ b/PO/de.po @@ -0,0 +1,199 @@ +# sgbackup - The SaveGameBackup tool +# Copyright (C) 2024,2025 +# This file is distributed under the same license as the PACKAGE package. +# Christian Moser , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-03-05 10:15+0100\n" +"PO-Revision-Date: 2025-03-05 10:19+0100\n" +"Last-Translator: Christian Moser \n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.5\n" + +#: sgbackup/gui/_app.py:224 +msgid "Add a new game." +msgstr "Neues Spiel hinzufügen." + +#: sgbackup/gui/_app.py:232 +msgid "Manage new Steam-Apps." +msgstr "Neue Steam-Apps verwalten." + +#: sgbackup/gui/_app.py:242 +msgid "Backup all active and live Games." +msgstr "Alle aktiven und live Spiele sichern." + +#: sgbackup/gui/_app.py:276 +msgid "Key" +msgstr "Schlüssel" + +#: sgbackup/gui/_app.py:282 +msgid "Name" +msgstr "Name" + +#: sgbackup/gui/_app.py:290 +msgid "Active" +msgstr "Aktiv" + +#: sgbackup/gui/_app.py:296 sgbackup/gui/_app.py:864 +msgid "Live" +msgstr "Live" + +#: sgbackup/gui/_app.py:455 +msgid "No icon-name for sgtype {}" +msgstr "Kein Icon-Name für sgtype {}" + +#: sgbackup/gui/_app.py:543 +#, python-brace-format +msgid "Do you want to create a new savegame for {game}?" +msgstr "Willst du ein neues SaveGame für {game} erstellen?" + +#: sgbackup/gui/_app.py:545 +msgid "The new savegame is added to the finsihed savegames for the game." +msgstr "" +"Das Neue SaveGame wird zu den beendeten Speicherständen für dieses Spiel " +"hinzugefügt." + +#: sgbackup/gui/_app.py:557 +msgid "Backup the SaveGames for this game." +msgstr "Sichere das SaveGame für dieses Spiel." + +#: sgbackup/gui/_app.py:569 +msgid "" +"Remove this game.\n" +"This also deletes the game configuration file!!!" +msgstr "" +"Lösche dieses Spiel!\n" +"Dies wird auch die Konfigurationsdatei des Spiels " +"löschen!!!" + +#: sgbackup/gui/_app.py:663 +#, python-brace-format +msgid "" +"Do you really want to remove the game {game}?" +msgstr "Wills du wirklich das Spiel {game} löschen?" + +#: sgbackup/gui/_app.py:666 +msgid "Removing games cannot be undone!!!" +msgstr "Das Löschen von Spielen kann nicht Rückgängig gemacht werden!!!" + +#: sgbackup/gui/_app.py:859 +msgid "OS" +msgstr "OS" + +#: sgbackup/gui/_app.py:869 +msgid "Savegame name" +msgstr "Speicherstandname" + +#: sgbackup/gui/_app.py:875 +msgid "Timestamp" +msgstr "Zeitstempel" + +#: sgbackup/gui/_app.py:880 +msgid "Size" +msgstr "Größe" + +#: sgbackup/gui/_app.py:1066 +msgid "%m.%d.%Y %H:%M:%S" +msgstr "%d.%m.%Y %H:%M:%S" + +#: sgbackup/gui/_app.py:1099 +msgid "Restore the SaveGameBackup." +msgstr "Stelle das Speicherstandbackup wieder her." + +#: sgbackup/gui/_app.py:1104 +msgid "Convert to another SaveGameBackup." +msgstr "Konvertiere zu anderen Speicherstandbackup" + +#: sgbackup/gui/_app.py:1113 +msgid "Delete this SaveGameBackup." +msgstr "Lösche dieses Speicherstandbackup." + +#: sgbackup/gui/_app.py:1170 +msgid "SGBackup" +msgstr "SGBackup" + +#: sgbackup/gui/_app.py:1232 sgbackup/gui/_app.py:1300 +#, python-brace-format +msgid "" +"{games} Games -- {active} Games active -- {live} Games live -- {finished} " +"Games finished" +msgstr "" +"{games} Spile -- {active} Spiele aktiv -- {live} Spiele live -- {finished} " +"Spiele beendet" + +#: sgbackup/gui/appmenu.ui:6 +msgid "_Game" +msgstr "_Spiel" + +#: sgbackup/gui/appmenu.ui:9 +msgid "_Add Game" +msgstr "Spiel _hinzufügen" + +#: sgbackup/gui/appmenu.ui:14 +msgid "_Steam" +msgstr "_Steam" + +#: sgbackup/gui/appmenu.ui:17 +msgid "New Steam Apps" +msgstr "Neue Steam-App" + +#: sgbackup/gui/appmenu.ui:21 +msgid "Manage Steam Libraries" +msgstr "Steam Bibliotheken verwalten" + +#: sgbackup/gui/appmenu.ui:27 +msgid "_Epic" +msgstr "_Epic" + +#: sgbackup/gui/appmenu.ui:30 +msgid "_GoG" +msgstr "_GoG" + +#: sgbackup/gui/appmenu.ui:35 +msgid "_Settings" +msgstr "_Einstellungen" + +#: sgbackup/gui/appmenu.ui:42 +msgid "_Help" +msgstr "_Hilfe" + +#: sgbackup/gui/appmenu.ui:47 +msgid "_About SGBackup" +msgstr "_Über SGBackup" + +#: sgbackup/gui/appmenu.ui:56 +msgid "_Quit" +msgstr "_Beenden" + +#: sgbackup/gui/appmenu.ui:65 +msgid "Convert to _Windows" +msgstr "Nach _Windows konvertieren" + +#: sgbackup/gui/appmenu.ui:69 +msgid "Convert to _Linux" +msgstr "Nach _Linux konvertieren" + +#: sgbackup/gui/appmenu.ui:73 +msgid "Convert to _Mac OS" +msgstr "Nach _MacOS konvertieren" + +#: sgbackup/gui/appmenu.ui:77 +msgid "Convert to _Steam Windows" +msgstr "Nach _Steam-Windows konvertieren" + +#: sgbackup/gui/appmenu.ui:81 +msgid "Convert to Steam Linux" +msgstr "Nach _Steam-Linux konvertieren" + +#: sgbackup/gui/appmenu.ui:85 +msgid "Convert to Steam Mac OS" +msgstr "Nach _Steam-MacOS konvertieren" diff --git a/msys-install.sh b/msys-install.sh index 116ed24..583c366 100755 --- a/msys-install.sh +++ b/msys-install.sh @@ -25,6 +25,19 @@ fi . "$venv/bin/activate" cd $PROJECT_DIR + +# translations +make translations +MSYS_LOCALEDIR="${MSYSTEM_PREFIX}/share/locale" +for i in $( cat "$PROJECT_ROOT/LINGUAS" ); do + mo="${PROJECT_ROOT}/sgbackup/locale/${i}/sgbackup.mo" + localedir="${MSYS_LOCALEDIR}/${i}/LC_MESSAGES" + + [ ! -d "$localedir" ] && mkdir -p "$localedir" + cp -v "$mo" "$localedir" +done + +# install pip install --verbose --user . bindir=$( realpath "$venv/bin" ) diff --git a/scripts/extract_localestrings.sh b/scripts/extract_localestrings.sh new file mode 100755 index 0000000..a8fc478 --- /dev/null +++ b/scripts/extract_localestrings.sh @@ -0,0 +1,24 @@ +#!/bin/sh +self="$( realpath "$0" )" +scriptdir="$( dirname "$self" )" +project_root="$( dirname "$( dirname "$self" )" )" + +PO_DIR="${project_root}/PO" + +cd "$prject_root" +sources="${PO_DIR}/SOURCES" +ui_sources="${PO_DIR}/UI.SOURCES" +pot_file="${PO_DIR}/messages.pot" + +[ -f "$sources" ] && rm "$sources" +[ -f "$ui_sources" ] && rm "$ui_sources" + +for i in $( find "./sgbackup" | egrep '\.py$' ); do + echo "${i#./}" >> "$sources" +done +for i in $( find "./sgbackup" | egrep '\.ui$' ); do + echo "${i#./}" >> "$ui_sources" +done + +/usr/bin/xgettext -o "$pot_file" --force-po -l Python -c -k_ -kN_ -kQ_ -f "$sources" --color=auto -n --copyright-holder="sgbackup Team" --package-name="sgbackup" +/usr/bin/xgettext -o "$pot_file" -l Glade -j -f "$ui_sources" --color=auto -n diff --git a/scripts/make_translations.sh b/scripts/make_translations.sh new file mode 100755 index 0000000..d4a03f9 --- /dev/null +++ b/scripts/make_translations.sh @@ -0,0 +1,21 @@ +#!/bin/sh +self="$( realpath "$0" )" +scriptdir="$( dirname "$self" )" +project_root="$( dirname "$( dirname "$self" )" )" + +PO_DIR="${project_root}/PO" +LINGUAS="${PO_DIR}/LINGUAS" +LOCALEDIR="${project_root}/sgbackup/locale" + +for i in $( cat "$LINGUAS" );do + po="${PO_DIR}/${i}.po" + + [ ! -f "$po" ] && continue + + msgdir="${LOCALEDIR}/${i}/LC_MESSAGES" + mo="${msgdir}/sgbackup.mo" + + [ ! -d $msgdir ] && mkdir -pv "$msgdir" + + msgfmt -o "$mo" "$po" +done diff --git a/scripts/update_po.sh b/scripts/update_po.sh new file mode 100755 index 0000000..6a6223d --- /dev/null +++ b/scripts/update_po.sh @@ -0,0 +1,18 @@ +#!/bin/sh +self="$( realpath "$0" )" +scriptdir="$( dirname "$0" )" +project_root="$( dirname "$(dirname "$self" )" )" + +PO_DIR="${project_root}/PO" +LINGUAS="${PO_DIR}/LINGUAS" + +pot_file="${PO_DIR}/messages.pot" + +for i in $( cat "$LINGUAS" ); do + po="${PO_DIR}/${i}.po" + if [ ! -f "$po" ]; then + cp "$pot_file" "$po" + else + msgmerge --update --previous --lang="$i" "$po" "$pot_file" + fi +done diff --git a/setup.py b/setup.py index eb15436..048a1e8 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ setup( 'sgbackup.curses', 'sgbackup.help', 'sgbackup.gui', + 'sgbackup.locale', ], package_data={ 'sgbackup':[ @@ -38,6 +39,10 @@ setup( 'sgbackup.gui': [ '*.ui' ], + 'sgbackup.locale': [ + '*/*.mo', + '*/LC_MESSAGES/*.po', + ], }, platforms=['win32','linux'] ) diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py index 634df9c..aaf20e0 100644 --- a/sgbackup/gui/_app.py +++ b/sgbackup/gui/_app.py @@ -18,6 +18,8 @@ from gi.repository import Gtk,Gio,Gdk,GLib from gi.repository.GObject import GObject,Signal,Property,SignalFlags,BindingFlags +from ..i18n import gettext as _, noop as N_,pgettext,npgettext,ngettext + import rapidfuzz import logging; logger=logging.getLogger(__name__) @@ -30,7 +32,14 @@ from ..settings import settings from ._settingsdialog import SettingsDialog from ._gamedialog import GameDialog from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS,SavegameType -from ._steam import SteamLibrariesDialog,NewSteamAppsDialog,NoNewSteamAppsDialog +from ._steam import ( + SteamLibrariesDialog, + NewSteamAppsDialog, + NoNewSteamAppsDialog, + NoIgnoredSteamAppsDialog, + SteamIgnoreAppsDialog, +) + from ..steam import Steam from ._backupdialog import BackupSingleDialog,BackupManyDialog from ..archiver import ArchiverManager @@ -160,23 +169,6 @@ class GameViewNameSorter(Gtk.Sorter): else: return Gtk.Ordering.EQUAL -class GameViewColumnSorter(Gtk.ColumnViewSorter): - def __init__(self,*args,**kwargs): - Gtk.ColumnViewSorter.__init__(self,*args,**kwargs) - self.__key_sorter = GameViewKeySorter - self.__name_sorter = GameViewNameSorter - - def do_compare(self,item1,item2): - game1 = item1.game - game2 = item2.game - column = self.get_primary_sort_column() - sort_ascending = (self.get_primary_sort_order() == Gtk.SortType.ASCENDING) - if column == 1: - self.__key_sorter.sort_ascending == sort_ascending - return self.__key_sorter.do_compare(game1,game2) - else: - self.__name_sorter.sort_ascending = sort_ascending - return self.__name_sorter.do_comapre(game1,game2) class GameViewMatchSorter(Gtk.Sorter): def do_compare(self,item1:GameViewData,item2:GameViewData): @@ -229,16 +221,15 @@ class GameView(Gtk.Box): icon.set_pixel_size(16) add_game_button=Gtk.Button() add_game_button.set_child(icon) - add_game_button.set_tooltip_text("Add a new game.") + add_game_button.set_tooltip_text(_("Add a new game.")) add_game_button.connect('clicked',self._on_add_game_button_clicked) self.actionbar.pack_start(add_game_button) - - + icon = Gtk.Image.new_from_icon_name('steam-svgrepo-com-symbolic') icon.set_pixel_size(16) new_steam_games_button=Gtk.Button() new_steam_games_button.set_child(icon) - new_steam_games_button.set_tooltip_text("Manage new Steam-Apps.") + new_steam_games_button.set_tooltip_text(_("Manage new Steam-Apps.")) new_steam_games_button.connect('clicked',self._on_new_steam_games_button_clicked) self.actionbar.pack_start(new_steam_games_button) @@ -248,7 +239,7 @@ class GameView(Gtk.Box): icon.set_pixel_size(16) backup_active_live_button = Gtk.Button() backup_active_live_button.set_child(icon) - backup_active_live_button.set_tooltip_markup("Backup all active and live Games.") + backup_active_live_button.set_tooltip_markup(_("Backup all active and live Games.")) backup_active_live_button.connect('clicked',self._on_backup_active_live_button_clicked) self.actionbar.pack_start(backup_active_live_button) @@ -267,8 +258,10 @@ class GameView(Gtk.Box): self.__columnview = Gtk.ColumnView() columnview_sorter = self.columnview.get_sorter() self.__liststore = Gio.ListStore.new(GameViewData) - for g in GameManager.get_global().games.values(): - self.__liststore.append(GameViewData(g)) + + gamemanager = GameManager.get_global() + for g in gamemanager.games.values(): + self._liststore.append(GameViewData(g)) self.__filter_model = Gtk.FilterListModel.new(self._liststore,None) self.__sort_model = Gtk.SortListModel.new(self.__filter_model,columnview_sorter) @@ -280,13 +273,13 @@ class GameView(Gtk.Box): factory_key = Gtk.SignalListItemFactory.new() factory_key.connect('setup',self._on_key_column_setup) factory_key.connect('bind',self._on_key_column_bind) - column_key = Gtk.ColumnViewColumn.new("Key",factory_key) + column_key = Gtk.ColumnViewColumn.new(_("Key"),factory_key) column_key.set_sorter(GameViewKeySorter()) factory_name = Gtk.SignalListItemFactory.new() factory_name.connect('setup',self._on_name_column_setup) factory_name.connect('bind',self._on_name_column_bind) - column_name = Gtk.ColumnViewColumn.new("Name",factory_name) + column_name = Gtk.ColumnViewColumn.new(_("Name"),factory_name) column_name.set_sorter(GameViewNameSorter()) column_name.set_expand(True) @@ -294,13 +287,13 @@ class GameView(Gtk.Box): factory_active.connect('setup',self._on_active_column_setup) factory_active.connect('bind',self._on_active_column_bind) factory_active.connect('unbind',self._on_active_column_unbind) - column_active = Gtk.ColumnViewColumn.new("Active",factory_active) + column_active = Gtk.ColumnViewColumn.new(_("Active"),factory_active) factory_live = Gtk.SignalListItemFactory.new() factory_live.connect('setup',self._on_live_column_setup) factory_live.connect('bind',self._on_live_column_bind) factory_live.connect('unbind',self._on_live_column_unbind) - column_live = Gtk.ColumnViewColumn.new("Live",factory_live) + column_live = Gtk.ColumnViewColumn.new(_("Live"),factory_live) factory_actions = Gtk.SignalListItemFactory.new() factory_actions.connect('setup',self._on_actions_column_setup) @@ -308,15 +301,11 @@ class GameView(Gtk.Box): #factory_actions.connect('unbind',self._on_actions_column_unbind) column_actions = Gtk.ColumnViewColumn.new("",factory_actions) - self.__selection_model = Gtk.SingleSelection() - self.__selection_model.set_autoselect(False) - self.__selection_model.set_can_unselect(True) - self.__selection_model.set_model(self.__sort_model) - self.columnview.set_model(self.__selection_model) - + self.__selection_model = Gtk.SingleSelection(autoselect=False,can_unselect=True,model=self.__sort_model) self.columnview.set_vexpand(True) self.columnview.set_hexpand(True) + self.columnview.set_single_click_activate(True) self.columnview.append_column(column_icon) self.columnview.append_column(column_key) self.columnview.append_column(column_name) @@ -324,16 +313,18 @@ class GameView(Gtk.Box): self.columnview.append_column(column_live) self.columnview.append_column(column_actions) self.columnview.sort_by_column(column_name,Gtk.SortType.ASCENDING) - self.columnview.set_single_click_activate(True) - self.columnview.get_vadjustment().set_value(0) + self.columnview.set_model(self.__selection_model) + + + cv_vadjustment = self.columnview.get_vadjustment() + cv_vadjustment.set_value(cv_vadjustment.get_lower()) scrolled.set_child(self.columnview) self.append(scrolled) @property def _liststore(self)->Gio.ListStore: """ - The `Gio.ListStore` that holds the list of installed games. - + The `Gio.ListStore` that holds the list of registered games. :type: Gio.ListStore """ @@ -461,7 +452,7 @@ class GameView(Gtk.Box): def transform_to_icon_name(_binding,sgtype): icon_name = SAVEGAME_TYPE_ICONS[sgtype] if sgtype in SAVEGAME_TYPE_ICONS else "" if not icon_name: - logger.warning("No icon-name for sgtype {}".format(sgtype.value)) + logger.warning(_("No icon-name for sgtype {}").format(sgtype.value)) return icon_name icon = item.get_child() @@ -549,9 +540,9 @@ class GameView(Gtk.Box): if not game.is_live: dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO) dialog.set_transient_for(self.get_root()) - dialog.props.text = "Do you want to create a new savegame for {game}?".format(game=game.name) + dialog.props.text = _("Do you want to create a new savegame for {game}?").format(game=game.name) dialog.props.use_markup = True - dialog.props.secondary_text = "The new savegame is added to the finsihed savegames for the game." + dialog.props.secondary_text = _("The new savegame is added to the finsihed savegames for the game.") dialog.props.secondary_use_markup = False dialog.connect('response',on_dialog_response) dialog.present() @@ -563,7 +554,7 @@ class GameView(Gtk.Box): icon = Gtk.Image.new_from_icon_name('document-save-symbolic') child.backup_button = Gtk.Button() child.backup_button.set_child(icon) - child.backup_button.set_tooltip_text("Backup the SaveGames for this game.") + child.backup_button.set_tooltip_text(_("Backup the SaveGames for this game.")) child.append(child.backup_button) icon = Gtk.Image.new_from_icon_name('document-edit-symbolic') @@ -575,7 +566,7 @@ class GameView(Gtk.Box): icon = Gtk.Image.new_from_icon_name('list-remove-symbolic') child.remove_button = Gtk.Button() child.remove_button.set_child(icon) - child.remove_button.set_tooltip_markup("Remove this game.\nThis also deletes the game configuration file!!!") + child.remove_button.set_tooltip_markup(_("Remove this game.\nThis also deletes the game configuration file!!!")) child.append(child.remove_button) item.set_child(child) @@ -669,10 +660,10 @@ class GameView(Gtk.Box): game = item.get_item().game dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO, - text="Do you really want to remove the game {game}?".format( + text=_("Do you really want to remove the game {game}?").format( game=game.name), use_markup=True, - secondary_text="Removing games cannot be undone!!!") + secondary_text=_("Removing games cannot be undone!!!")) dialog.set_transient_for(self.get_root()) dialog.set_modal(False) dialog.connect('response',on_dialog_response,game) @@ -692,10 +683,16 @@ class BackupViewData(GObject): basename = os.path.basename(filename) parts = basename.split('.') - self.__savegame_name = parts[0] - self.__timestamp = DateTime.strptime(parts[1],"%Y%m%d-%H%M%S") - self.__is_live = parts[3] == 'live' - self.__sgtype = SavegameType.from_string(parts[2]) + try: + self.__savegame_name = parts[0] + self.__timestamp = DateTime.strptime(parts[1],"%Y%m%d-%H%M%S") + self.__sgtype = SavegameType.from_string(parts[2]) + self.__is_live = parts[3] == 'live' + except: + self.__savegame_name = basename + self.__timestamp = "Unknown" + self.__sgtype = SavegameType.UNSET + self.__is_live = True WINDOWS_TYPES = [ @@ -859,28 +856,28 @@ class BackupView(Gtk.Box): sgos_factory = Gtk.SignalListItemFactory() sgos_factory.connect('setup',self._on_sgos_column_setup) sgos_factory.connect('bind',self._on_sgos_column_bind) - sgos_column = Gtk.ColumnViewColumn.new("OS",sgos_factory) + sgos_column = Gtk.ColumnViewColumn.new(_("OS"),sgos_factory) live_factory = Gtk.SignalListItemFactory() live_factory.connect('setup',self._on_live_column_setup) live_factory.connect('bind',self._on_live_column_bind) - live_column = Gtk.ColumnViewColumn.new("Live",live_factory) + live_column = Gtk.ColumnViewColumn.new(_("Live"),live_factory) sgname_factory = Gtk.SignalListItemFactory() sgname_factory.connect('setup',self._on_savegamename_column_setup) sgname_factory.connect('bind',self._on_savegamename_column_bind) - sgname_column = Gtk.ColumnViewColumn.new("Savegame name",sgname_factory) + sgname_column = Gtk.ColumnViewColumn.new(_("Savegame name"),sgname_factory) sgname_column.set_expand(True) timestamp_factory = Gtk.SignalListItemFactory() timestamp_factory.connect('setup',self._on_timestamp_column_setup) timestamp_factory.connect('bind',self._on_timestamp_column_bind) - timestamp_column = Gtk.ColumnViewColumn.new("Timestamp",timestamp_factory) + timestamp_column = Gtk.ColumnViewColumn.new(_("Timestamp"),timestamp_factory) size_factory = Gtk.SignalListItemFactory() size_factory.connect('setup',self._on_size_column_setup) size_factory.connect('bind',self._on_size_column_bind) - size_column = Gtk.ColumnViewColumn.new("Size",size_factory) + size_column = Gtk.ColumnViewColumn.new(_("Size"),size_factory) actions_factory = Gtk.SignalListItemFactory() actions_factory.connect('setup',self._on_actions_column_setup) @@ -1066,7 +1063,7 @@ class BackupView(Gtk.Box): label = item.get_child() data = item.get_item() label.set_markup("{}".format( - GLib.markup_escape_text(data.timestamp.strftime("%d.%m.%Y %H:%M:%S")))) + GLib.markup_escape_text(data.timestamp.strftime(_("%m.%d.%Y %H:%M:%S"))))) def _on_size_column_setup(self,factory,item): label = Gtk.Label() @@ -1099,12 +1096,12 @@ class BackupView(Gtk.Box): icon = Gtk.Image.new_from_icon_name('document-revert-symbolic') icon.set_pixel_size(16) child.restore_button.set_child(icon) - child.restore_button.set_tooltip_text("Restore the SaveGameBackup.") + child.restore_button.set_tooltip_text(_("Restore the SaveGameBackup.")) child.append(child.restore_button) child.convert_button = Gtk.MenuButton() child.convert_button.set_icon_name('document-properties-symbolic') - child.convert_button.set_tooltip_text("Convert to another SaveGameBackup.") + child.convert_button.set_tooltip_text(_("Convert to another SaveGameBackup.")) child.convert_button.set_menu_model(self.__convertmenu) child.convert_button.set_direction(Gtk.ArrowType.UP) child.append(child.convert_button) @@ -1113,7 +1110,7 @@ class BackupView(Gtk.Box): icon = Gtk.Image.new_from_icon_name('list-remove-symbolic') icon.set_pixel_size(16) child.delete_button.set_child(icon) - child.delete_button.set_tooltip_text("Delete this SaveGameBackup.") + child.delete_button.set_tooltip_text(_("Delete this SaveGameBackup.")) child.append(child.delete_button) item.set_child(child) @@ -1170,7 +1167,7 @@ class AppWindow(Gtk.ApplicationWindow): :param application: The `Application` this window belongs to, defaults to `None`. :type application: Application, optional """ - kwargs['title'] = "SGBackup" + kwargs['title'] = _("SGBackup") if (application is not None): kwargs['application']=application @@ -1232,7 +1229,7 @@ class AppWindow(Gtk.ApplicationWindow): if game.is_active: n_active += 1 - self.statusbar.push(0,'{games} Games -- {active} Games active -- {live} Games live -- {finished} Games finished'.format( + self.statusbar.push(0,_('{games} Games -- {active} Games active -- {live} Games live -- {finished} Games finished').format( games=n_games, active=n_active, live=n_live, @@ -1300,7 +1297,7 @@ class AppWindow(Gtk.ApplicationWindow): if game.is_active: n_active += 1 - self.statusbar.push(0,'{games} Games -- {active} Games active -- {live} Games live -- {finished} Games finished'.format( + self.statusbar.push(0,_('{games} Games -- {active} Games active -- {live} Games live -- {finished} Games finished').format( games=n_games, active=n_active, live=n_live, @@ -1385,6 +1382,10 @@ class Application(Gtk.Application): action_steam_new_apps.connect('activate',self._on_action_steam_new_apps) self.add_action(action_steam_new_apps) + action_steam_manage_ignore = Gio.SimpleAction.new('steam-manage-ignore',None) + action_steam_manage_ignore.connect('activate',self._on_action_steam_manage_ignore) + self.add_action(action_steam_manage_ignore) + # add accels self.set_accels_for_action('app.quit',["q"]) @@ -1446,6 +1447,21 @@ class Application(Gtk.Application): dialog = NoNewSteamAppsDialog(self.appwindow) dialog.present() + def _on_steam_ignore_apps_dialog_response(self,dialog,response): + self.appwindow.refresh() + + def _on_action_steam_manage_ignore(self,action,param): + + steam = Steam() + if not steam.ignore_apps: + dialog = NoIgnoredSteamAppsDialog(self.appwindow) + dialog.present() + else: + dialog = SteamIgnoreAppsDialog(self.appwindow) + dialog.connect_after("response",self._on_steam_ignore_apps_dialog_response) + + dialog.present() + def new_settings_dialog(self)->SettingsDialog: """ new_settings_dialog Create a new `SettingsDialog`. diff --git a/sgbackup/gui/_steam.py b/sgbackup/gui/_steam.py index ab41897..aff5c17 100644 --- a/sgbackup/gui/_steam.py +++ b/sgbackup/gui/_steam.py @@ -414,4 +414,32 @@ class NoNewSteamAppsDialog(Gtk.MessageDialog): def do_response(self,response): self.hide() - self.destroy() \ No newline at end of file + self.destroy() + +class NoIgnoredSteamAppsDialog(Gtk.MessageDialog): + def __init__(self,parent:Gtk.Window|None=None): + Gtk.MessageDialog.__init__(self,buttons=Gtk.ButtonsType.OK) + if parent: + self.set_transient_for(parent) + + self.props.text = "There are no Steam-Apps that are ignored!" + self.props.use_markup = False + + def do_response(self,response): + self.hide() + self.destroy() + + +class SteamIgnoreAppsDialog(Gtk.Dialog): + def __init__(self,parent:Gtk.Window|None=None): + Gtk.Dialog.__init__(self) + if parent: + self.set_transient_for(parent) + self.set_modal(False) + + self.add_button("Close",Gtk.ResponseType.OK) + + def do_response(self,response): + self.hide() + self.destroy() + \ No newline at end of file diff --git a/sgbackup/gui/appmenu.ui b/sgbackup/gui/appmenu.ui index 0aac121..bda9d6d 100644 --- a/sgbackup/gui/appmenu.ui +++ b/sgbackup/gui/appmenu.ui @@ -45,7 +45,7 @@
_About SGBackup - app.about + app.about
diff --git a/sgbackup/i18n.py b/sgbackup/i18n.py new file mode 100644 index 0000000..2ddb9eb --- /dev/null +++ b/sgbackup/i18n.py @@ -0,0 +1,30 @@ +############################################################################### +# sgbackup - The SaveGame Backup tool # +# Copyright (C) 2024,2025 Christian Moser # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +############################################################################### + +from os.path import dirname,join +import gettext as _gettext + +TEXTDOMAIN="sgbackup" + +_gettext.bindtextdomain(TEXTDOMAIN,join(dirname(__file__),'locale')) + +gettext = lambda s: _gettext.dgettext(TEXTDOMAIN,s) +pgettext = lambda context,s: _gettext.dpgettext(TEXTDOMAIN,context,s) +ngettext = lambda singular,plural,n: _gettext.dngettext(TEXTDOMAIN,singular,plural,n) +npgettext = lambda context,singular,plural,n: _gettext.dnpgettext(TEXTDOMAIN,context,singular,plural,n) +noop = lambda s: s diff --git a/sgbackup/locale/__init__.py b/sgbackup/locale/__init__.py new file mode 100644 index 0000000..4107e21 --- /dev/null +++ b/sgbackup/locale/__init__.py @@ -0,0 +1 @@ +# just here to make a package