diff --git a/MANIFEST.in b/MANIFEST.in index c26f8b9..b52bf97 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,3 +2,4 @@ include jellyfin_mpv_shim/systray.png recursive-include jellyfin_mpv_shim/webclient_view/webclient * recursive-include jellyfin_mpv_shim/integration * recursive-include jellyfin_mpv_shim/default_shader_pack * +recursive-include jellyfin_mpv_shim/messages *.mo diff --git a/README.md b/README.md index 2994e2a..488433d 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,7 @@ need to. - Update checks are performed when playing media, once per day. - `notify_updates` - Display update notification when playing media. Default: `true` - Notification will only display once until the application is restarted. + - `lang` - Allows overriding system locale. (Enter a language code.) Default: `null` ### MPV Configuration @@ -477,6 +478,25 @@ If you are on Windows there are additional dependencies. Please see the Windows 6. Install any platform-specific dependencies from the respective install tutorials. 7. You should now be able to run the program with `./run.py` or `./run-desktop.py`. Installation is possible with `sudo pip3 install .`. +### Translation + +This project uses gettext for translation. The current template language file is `base.pot` in `jellyfin_mpv_shim/messages/`. + +To regenerate `base.pot`: +```bash +pygettext --default-domain=base -o jellyfin_mpv_shim/messages/base.pot jellyfin_mpv_shim/*.py jellyfin_mpv_shim/**/*.py +``` + +To update an existing translation with new strings: +```bash +msgmerge --update jellyfin_mpv_shim/messages/es/LC_MESSAGES/base.po jellyfin_mpv_shim/messages/base.pot +``` + +To compile all `*.po` files to `*.mo`: +```bash +find -iname '*.po' | while read -r file; do msgfmt "$file" -o "${file%.*}.mo"; done +``` + ## Linux Installation You can [install the software from flathub](https://flathub.org/apps/details/com.github.iwalton3.jellyfin-mpv-shim). The pip installation is less integrated but takes up less space if you're not already using flatpak. diff --git a/build-win-32.bat b/build-win-32.bat index 5429262..fed5589 100644 --- a/build-win-32.bat +++ b/build-win-32.bat @@ -8,7 +8,7 @@ rem The desktop version (and corresponding shim shortcut) do work, however. rem Edge-based build rem "C:\Program Files (x86)\Python37-32\Scripts\pyinstaller" -w --add-binary "mpv32\mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --icon jellyfin.ico run-desktop-edge.py rem CEF-based build -"C:\Program Files (x86)\Python37-32\Scripts\pyinstaller" -w --add-binary "mpv32\mpv-1.dll;." --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py +"C:\Program Files (x86)\Python37-32\Scripts\pyinstaller" -w --add-binary "mpv32\mpv-1.dll;." --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py xcopy /E /Y cef32\cefpython3 dist\run-desktop\ xcopy /E /Y mpv32\mpv-1.dll dist\run-desktop\ del dist\run-desktop\run-desktop.exe.manifest diff --git a/build-win-dbg.bat b/build-win-dbg.bat index a3326c1..4e7997f 100644 --- a/build-win-dbg.bat +++ b/build-win-dbg.bat @@ -4,10 +4,10 @@ set PATH=%PATH%;%CD% rem Edge-based build rem pyinstaller -c --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --icon jellyfin.ico run-desktop-edge.py rem CEF-based build -pyinstaller -c --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py +pyinstaller -c --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py xcopy /E /Y cef\cefpython3 dist\run-desktop\ del dist\run-desktop\run-desktop.exe.manifest copy hidpi.manifest dist\run-desktop\run-desktop.exe.manifest rd /s /q __pycache__ build -pyinstaller -cF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py +pyinstaller -cF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py pause diff --git a/build-win.bat b/build-win.bat index 696d694..e7bc37e 100644 --- a/build-win.bat +++ b/build-win.bat @@ -4,10 +4,10 @@ set PATH=%PATH%;%CD% rem Edge-based build rem pyinstaller -w --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --icon jellyfin.ico run-desktop-edge.py rem CEF-based build -pyinstaller -w --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py +pyinstaller -w --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\webclient_view\webclient;jellyfin_mpv_shim\webclient_view\webclient" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --icon jellyfin.ico run-desktop.py xcopy /E /Y cef\cefpython3 dist\run-desktop\ del dist\run-desktop\run-desktop.exe.manifest copy hidpi.manifest dist\run-desktop\run-desktop.exe.manifest rd /s /q __pycache__ build -pyinstaller -wF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py +pyinstaller -wF --add-binary "mpv-1.dll;." --add-data "jellyfin_mpv_shim\systray.png;jellyfin_mpv_shim" --add-data "jellyfin_mpv_shim\default_shader_pack;jellyfin_mpv_shim\default_shader_pack" --add-data "jellyfin_mpv_shim\messages;jellyfin_mpv_shim\messages" --add-data "jellyfin_mpv_shim\display_mirror\index.html;jellyfin_mpv_shim\display_mirror" --add-data "jellyfin_mpv_shim\display_mirror\jellyfin.css;jellyfin_mpv_shim\display_mirror" --add-binary "Microsoft.Toolkit.Forms.UI.Controls.WebView.dll;." --exclude-module cefpython3 --icon jellyfin.ico run.py "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" "Jellyfin MPV Desktop.iss" diff --git a/jellyfin_mpv_shim/bulk_subtitle.py b/jellyfin_mpv_shim/bulk_subtitle.py index 53ab058..f1d33ba 100644 --- a/jellyfin_mpv_shim/bulk_subtitle.py +++ b/jellyfin_mpv_shim/bulk_subtitle.py @@ -1,6 +1,8 @@ from collections import namedtuple from .utils import get_sub_display_title -import urllib.parse +from .i18n import _ +# TODO: Should probably support automatic profiles for languages other than English and Japanese... + import time import logging @@ -15,7 +17,7 @@ keep_messages = 6 def render_message(message, show_text): log.info(message) messages.append(message) - text = "Selecting Tracks..." + text = _("Selecting Tracks...") for message in messages[-6:]: text += "\n " + message show_text(text,2**30,1) @@ -41,9 +43,9 @@ def process_series(mode, player, m_raid=None, m_rsid=None): audio_list = [Audio(s.get("Index"), s.get("Language"), s.get("Title"), s.get("DisplayTitle")) for s in media_source["MediaStreams"] if s.get("Type") == "Audio"] - subtitle_list = [Subtitle(s.get("Index"), s.get("Language"), s.get("Title"), s.get("IsForced"), - get_sub_display_title(s)) for s in media_source["MediaStreams"] - if s.get("Type") == "Subtitle"] + subtitle_list = [Subtitle(s.get("Index"), s.get("Language"), s.get("Title"), s.get("IsForced"), + get_sub_display_title(s)) for s in media_source["MediaStreams"] + if s.get("Type") == "Subtitle"] part = Part(media_source.get("Id"), audio_list, subtitle_list) aid = None @@ -63,7 +65,7 @@ def process_series(mode, player, m_raid=None, m_rsid=None): aid, sid = audio.id, subtitle.id success_ct += 1 elif audio: - render_message("{0}: No Subtitles".format(name), show_text) + render_message(_("{0}: No Subtitles").format(name), show_text) aid = audio.id partial_ct += 1 elif mode == "manual": @@ -86,26 +88,26 @@ def process_series(mode, player, m_raid=None, m_rsid=None): # This is a horrible hack to change the audio/subtitle settings without playing # the media. I checked the jelyfin source code and didn't find a better way. client.jellyfin.session_progress({ - "ItemId":part.id, + "ItemId": part.id, "AudioStreamIndex": aid, "SubtitleStreamIndex": sid }) else: - render_message("{0}: Fail".format(name), show_text) + render_message(_("{0}: Fail").format(name), show_text) if mode == "subbed": - render_message("Set Subbed: {0} ok, {1} fail".format( + render_message(_("Set Subbed: {0} ok, {1} fail").format( success_ct, count-success_ct), show_text) elif mode == "dubbed": - render_message("Set Dubbed: {0} ok, {1} audio only, {2} fail".format( + render_message(_("Set Dubbed: {0} ok, {1} audio only, {2} fail").format( success_ct, partial_ct, count-success_ct-partial_ct), show_text) elif mode == "manual": - render_message("Manual: {0} ok, {1} fail".format( + render_message(_("Manual: {0} ok, {1} fail").format( success_ct, count-success_ct), show_text) time.sleep(3) if c_aid: - render_message("Setting Current...", show_text) + render_message(_("Setting Current..."), show_text) player.put_task(player.set_streams, c_aid, c_sid) player.timeline_handle() @@ -116,7 +118,7 @@ def get_subbed(part): for audio in part.audio: lower_title = audio.name.lower() if audio.name is not None else "" - if audio.language_code != "jpn" and not "japan" in lower_title: + if audio.language_code != "jpn" and "japan" not in lower_title: continue if "commentary" in lower_title: continue @@ -127,7 +129,7 @@ def get_subbed(part): for subtitle in part.subtitle: lower_title = subtitle.name.lower() if subtitle.name is not None else "" - if subtitle.language_code != "eng" and not "english" in lower_title: + if subtitle.language_code != "eng" and "english" not in lower_title: continue if subtitle.is_forced: continue @@ -148,7 +150,7 @@ def get_dubbed(part): for audio in part.audio: lower_title = audio.name.lower() if audio.name is not None else "" - if audio.language_code != "eng" and not "english" in lower_title: + if audio.language_code != "eng" and "english" not in lower_title: continue if "commentary" in lower_title: continue @@ -159,7 +161,7 @@ def get_dubbed(part): for subtitle in part.subtitle: lower_title = subtitle.name.lower() if subtitle.name is not None else "" - if subtitle.language_code != "eng" and not "english" in lower_title: + if subtitle.language_code != "eng" and "english" not in lower_title: continue if subtitle.is_forced: sign_subtitles = subtitle diff --git a/jellyfin_mpv_shim/clients.py b/jellyfin_mpv_shim/clients.py index f9d1c44..e7caa9d 100644 --- a/jellyfin_mpv_shim/clients.py +++ b/jellyfin_mpv_shim/clients.py @@ -4,6 +4,7 @@ from .conf import settings from . import conffile from getpass import getpass from .constants import CLIENT_VERSION, USER_APP_NAME, USER_AGENT, APP_NAME +from .i18n import _ import sys import os.path @@ -41,18 +42,18 @@ class ClientManager(object): add_another = True while not is_logged_in or add_another: - server = input("Server URL: ") - username = input("Username: ") - password = getpass("Password: ") + server = input(_("Server URL: ")) + username = input(_("Username: ")) + password = getpass(_("Password: ")) is_logged_in = self.login(server, username, password) if is_logged_in: - log.info("Successfully added server.") - add_another = input("Add another server? [y/N] ") + log.info(_("Successfully added server.")) + add_another = input(_("Add another server? [y/N] ")) add_another = add_another in ("y", "Y", "yes", "Yes") else: - log.warning("Adding server failed.") + log.warning(_("Adding server failed.")) def client_factory(self): client = JellyfinClient(allow_multiple_clients=True) diff --git a/jellyfin_mpv_shim/conf.py b/jellyfin_mpv_shim/conf.py index dff0dde..0b35b5c 100644 --- a/jellyfin_mpv_shim/conf.py +++ b/jellyfin_mpv_shim/conf.py @@ -92,6 +92,7 @@ class Settings(object): "screenshot_menu": True, "check_updates": True, "notify_updates": True, + "lang": None, } def __getattr__(self, name): diff --git a/jellyfin_mpv_shim/display_mirror/__init__.py b/jellyfin_mpv_shim/display_mirror/__init__.py index c8241e1..4dd17e7 100644 --- a/jellyfin_mpv_shim/display_mirror/__init__.py +++ b/jellyfin_mpv_shim/display_mirror/__init__.py @@ -17,9 +17,9 @@ import webview # Python3-webview in Debian, pywebview in pypi from ..clients import clientManager from ..utils import get_text +from ..i18n import _ from . import helpers - # This makes me rather uncomfortable, but there's no easy way around this other than importing display_mirror in helpers. # Lambda needed because the 2.3 version of the JS api adds an argument even when not used. helpers.on_escape = lambda _x=None: load_idle() @@ -100,8 +100,8 @@ def get_html(server_address=None, item=None): jinja_vars = { 'random_backdrop': True, # Make the jinja template load some extra JS code for random backdrops 'backdrop_src': helpers.getRandomBackdropUrl(), # Preinitialise it with a random backdrop though - 'display_name': "Ready to cast", - 'overview': "\n\nSelect your media in Jellyfin and play it here", # FIME: Mention the player_name here + 'display_name': _("Ready to cast"), + 'overview': "\n\n" + _("Select your media in Jellyfin and play it here"), # FIME: Mention the player_name here } jinja_vars.update({ diff --git a/jellyfin_mpv_shim/gui_mgr.py b/jellyfin_mpv_shim/gui_mgr.py index d75cfd9..600582c 100644 --- a/jellyfin_mpv_shim/gui_mgr.py +++ b/jellyfin_mpv_shim/gui_mgr.py @@ -13,6 +13,7 @@ from .conffile import confdir from .clients import clientManager from .utils import get_resource from .log_utils import CustomFormatter +from .i18n import _ log = logging.getLogger('gui_mgr') @@ -132,7 +133,7 @@ class LoggerWindowProcess(Process): self.tk = tk root = tk.Tk() self.root = root - root.title("Application Log") + root.title(_("Application Log")) text = tk.Text(root) text.pack(side=tk.LEFT, fill=tk.BOTH, expand = tk.YES) text.config(wrap=tk.WORD) @@ -170,7 +171,7 @@ class PreferencesWindow(threading.Thread): else: self.handle("error") except Exception: - log.error("Error while adding server.", exc_info=1) + log.error("Error while adding server.", exc_info=True) self.handle("error") elif action == "remove": clientManager.remove_client(param) @@ -206,7 +207,9 @@ class PreferencesWindowProcess(Process): self.add_button.config(state=self.tk.NORMAL) self.remove_button.config(state=self.tk.NORMAL) elif action == "error": - self.messagebox.showerror("Add Server", "Could not add server.\nPlease check your connection infomation.") + self.messagebox.showerror( + _("Add Server"), + _("Could not add server.\nPlease check your connection infomation.")) self.add_button.config(state=self.tk.NORMAL) elif action == "die": self.root.destroy() @@ -222,7 +225,7 @@ class PreferencesWindowProcess(Process): self.serverList.set(["{0} ({1}, {2})".format( server["Name"], server["username"], - "Ok" if server["connected"] else "Fail" + _("Ok") if server["connected"] else _("Fail") ) for server in self.servers]) def run(self): @@ -231,7 +234,7 @@ class PreferencesWindowProcess(Process): self.tk = tk self.messagebox = messagebox root = tk.Tk() - root.title("Server Configuration") + root.title(_("Server Configuration")) self.root = root self.servers = {} @@ -254,17 +257,17 @@ class PreferencesWindowProcess(Process): c.grid_columnconfigure(0, weight=1) c.grid_rowconfigure(4, weight=1) - servername_label = ttk.Label(c, text='Server:') + servername_label = ttk.Label(c, text=_('Server:')) servername_label.grid(column=1, row=0, sticky=tk.E) self.servername = tk.StringVar() servername_box = ttk.Entry(c, textvariable=self.servername) servername_box.grid(column=2, row=0) - username_label = ttk.Label(c, text='Username:') + username_label = ttk.Label(c, text=_('Username:')) username_label.grid(column=1, row=1, sticky=tk.E) self.username = tk.StringVar() username_box = ttk.Entry(c, textvariable=self.username) username_box.grid(column=2, row=1) - password_label = ttk.Label(c, text='Password:') + password_label = ttk.Label(c, text=_('Password:')) password_label.grid(column=1, row=2, sticky=tk.E) self.password = tk.StringVar() password_box = ttk.Entry(c, textvariable=self.password, show="*") @@ -285,11 +288,11 @@ class PreferencesWindowProcess(Process): def close(): self.r_queue.put(("die", None)) - self.add_button = ttk.Button(c, text='Add Server', command=add_server) + self.add_button = ttk.Button(c, text=_('Add Server'), command=add_server) self.add_button.grid(column=2, row=3, pady=5, sticky=tk.E) - self.remove_button = ttk.Button(c, text='Remove Server', command=remove_server) + self.remove_button = ttk.Button(c, text=_('Remove Server'), command=remove_server) self.remove_button.grid(column=1, row=4, padx=5, pady=10, sticky=(tk.E, tk.S)) - close_button = ttk.Button(c, text='Close', command=close) + close_button = ttk.Button(c, text=_('Close'), command=close) close_button.grid(column=2, row=4, pady=10, sticky=(tk.E, tk.S)) serverlist.bind('<>', serverSelect) @@ -393,11 +396,11 @@ class STrayProcess(Process): self.icon_stop() menu_items = [ - MenuItem("Configure Servers", get_wrapper("show_preferences")), - MenuItem("Show Console", get_wrapper("show_console")), - MenuItem("Application Menu", get_wrapper("open_player_menu")), - MenuItem("Open Config Folder", get_wrapper("open_config_brs")), - MenuItem("Quit", die) + MenuItem(_("Configure Servers"), get_wrapper("show_preferences")), + MenuItem(_("Show Console"), get_wrapper("show_console")), + MenuItem(_("Application Menu"), get_wrapper("open_player_menu")), + MenuItem(_("Open Config Folder"), get_wrapper("open_config_brs")), + MenuItem(_("Quit"), die) ] icon = Icon(USER_APP_NAME, menu=Menu(*menu_items)) diff --git a/jellyfin_mpv_shim/i18n.py b/jellyfin_mpv_shim/i18n.py new file mode 100644 index 0000000..0dd46e8 --- /dev/null +++ b/jellyfin_mpv_shim/i18n.py @@ -0,0 +1,25 @@ +import gettext +import builtins + +from .conf import settings + +translation = gettext.NullTranslations() + + +def configure(): + global translation + from .utils import get_resource + messages_dir = get_resource("messages") + + if settings.lang is not None: + translation = gettext.translation("base", messages_dir, languages=[settings.lang], fallback=True) + else: + translation = gettext.translation("base", messages_dir, fallback=True) + + +def get_translation(): + return translation + + +def _(string: str) -> str: + return translation.gettext(string) diff --git a/jellyfin_mpv_shim/media.py b/jellyfin_mpv_shim/media.py index 0d1710b..30c6137 100644 --- a/jellyfin_mpv_shim/media.py +++ b/jellyfin_mpv_shim/media.py @@ -8,6 +8,7 @@ from sys import platform from .conf import settings from .utils import is_local_domain, get_profile, get_seq +from .i18n import _ log = logging.getLogger('media') @@ -101,7 +102,7 @@ class Video(object): if year is not None: title = "%s (%s)" % (title, year) setattr(self, "_title", title) - return getattr(self, "_title") + (" (Transcode)" if self.is_transcode else "") + return getattr(self, "_title") + (_(" (Transcode)") if self.is_transcode else "") def set_trs_override(self, video_bitrate, force_transcode): if force_transcode: @@ -282,9 +283,9 @@ class Media(object): return self.video if index < len(self.queue): - return Video(queue[index]["Id"], self) + return Video(self.queue[index]["Id"], self) - log.error("Media::get_video couldn't find video at index %s" % video) + log.error("Media::get_video couldn't find video at index %s" % index) def insert_items(self, items, append=False): items = [{ "PlaylistItemId": "playlistItem{0}".format(get_seq()), "Id": id_num } for id_num in items] diff --git a/jellyfin_mpv_shim/menu.py b/jellyfin_mpv_shim/menu.py index b5f00c8..81601a6 100644 --- a/jellyfin_mpv_shim/menu.py +++ b/jellyfin_mpv_shim/menu.py @@ -4,6 +4,7 @@ from .conf import settings from .utils import mpv_color_to_plex, get_sub_display_title from .video_profile import VideoProfileManager from .svp_integration import SVPManager +from .i18n import _ import time import logging @@ -24,23 +25,23 @@ TRANSCODE_LEVELS = ( ) COLOR_LIST = ( - ("White", "#FFFFFFFF"), - ("Yellow", "#FFFFEE00"), - ("Black", "#FF000000"), - ("Cyan", "#FF00FFFF"), - ("Blue", "#FF0000FF"), - ("Green", "#FF00FF00"), - ("Magenta", "#FFEE00EE"), - ("Red", "#FFFF0000"), - ("Gray", "#FF808080"), + (_("White"), "#FFFFFFFF"), + (_("Yellow"), "#FFFFEE00"), + (_("Black"), "#FF000000"), + (_("Cyan"), "#FF00FFFF"), + (_("Blue"), "#FF0000FF"), + (_("Green"), "#FF00FF00"), + (_("Magenta"), "#FFEE00EE"), + (_("Red"), "#FFFF0000"), + (_("Gray"), "#FF808080"), ) SIZE_LIST = ( - ("Tiny", 50), - ("Small", 75), - ("Normal", 100), - ("Large", 125), - ("Huge", 200), + (_("Tiny"), 50), + (_("Small"), 75), + (_("Normal"), 100), + (_("Large"), 125), + (_("Huge"), 200), ) HEX_TO_COLOR = {v:c for c,v in COLOR_LIST} @@ -101,39 +102,39 @@ class OSDMenu(object): if hasattr(player, 'osc'): player.osc = False - self.menu_title = "Main Menu" + self.menu_title = _("Main Menu") self.menu_selection = 0 if self.playerManager._video and not player.playback_abort: self.menu_list = [ - ("Change Audio", self.change_audio_menu), - ("Change Subtitles", self.change_subtitle_menu), - ("Change Video Quality", self.change_transcode_quality), - ("SyncPlay", self.playerManager.syncplay.menu_action), + (_("Change Audio"), self.change_audio_menu), + (_("Change Subtitles"), self.change_subtitle_menu), + (_("Change Video Quality"), self.change_transcode_quality), + (_("SyncPlay"), self.playerManager.syncplay.menu_action), ] if self.playerManager.update_check.new_version is not None: self.menu_list.insert(0, ( - "MPV Shim v{0} Release Info/Download".format(self.playerManager.update_check.new_version), + _("MPV Shim v{0} Release Info/Download").format(self.playerManager.update_check.new_version), self.playerManager.update_check.open )) if self.profile_menu is not None: - self.menu_list.append(("Change Video Playback Profile", self.profile_menu)) + self.menu_list.append((_("Change Video Playback Profile"), self.profile_menu)) if self.playerManager._video.parent.is_tv: - self.menu_list.append(("Auto Set Audio/Subtitles (Entire Series)", self.change_tracks_menu)) - self.menu_list.append(("Quit and Mark Unwatched", self.unwatched_menu_handle)) + self.menu_list.append((_("Auto Set Audio/Subtitles (Entire Series)"), self.change_tracks_menu)) + self.menu_list.append((_("Quit and Mark Unwatched"), self.unwatched_menu_handle)) if settings.screenshot_menu: - self.menu_list.append(("Screenshot", self.screenshot)) + self.menu_list.append((_("Screenshot"), self.screenshot)) else: self.menu_list = [] if self.profile_menu is not None: - self.menu_list.append(("Video Playback Profiles", self.profile_menu)) + self.menu_list.append((_("Video Playback Profiles"), self.profile_menu)) if self.svp_menu is not None and self.svp_menu.is_available(): - self.menu_list.append(("SVP Settings", self.svp_menu.menu_action)) + self.menu_list.append((_("SVP Settings"), self.svp_menu.menu_action)) self.menu_list.extend([ - ("Preferences", self.preferences_menu), - ("Close Menu", self.hide_menu) + (_("Preferences"), self.preferences_menu), + (_("Close Menu"), self.hide_menu) ]) self.refresh_menu() @@ -211,7 +212,7 @@ class OSDMenu(object): self.menu_action("back") def change_audio_menu(self): - self.put_menu("Select Audio Track") + self.put_menu(_("Select Audio Track")) selected_aid = self.playerManager._video.aid audio_streams = [s for s in self.playerManager._video.media_source["MediaStreams"] @@ -232,12 +233,12 @@ class OSDMenu(object): self.menu_action("back") def change_subtitle_menu(self): - self.put_menu("Select Subtitle Track") + self.put_menu(_("Select Subtitle Track")) selected_sid = self.playerManager._video.sid subtitle_streams = [s for s in self.playerManager._video.media_source["MediaStreams"] if s.get("Type") == "Subtitle"] - self.menu_list.append(["None", self.change_subtitle_menu_handle, -1]) + self.menu_list.append([_("None"), self.change_subtitle_menu_handle, -1]) for i, subtitle_track in enumerate(subtitle_streams): sid = subtitle_track.get("Index") self.menu_list.append([ @@ -263,9 +264,9 @@ class OSDMenu(object): def change_transcode_quality(self): handle = self.change_transcode_quality_handle - self.put_menu("Select Transcode Quality", [ - ("No Transcode", handle, "none"), - ("Maximum", handle, "max") + self.put_menu(_("Select Transcode Quality"), [ + (_("No Transcode"), handle, "none"), + (_("Maximum"), handle, "max") ]) for item in TRANSCODE_LEVELS: @@ -300,10 +301,10 @@ class OSDMenu(object): process_series("manual", self.playerManager, aid, sid) def change_tracks_menu(self): - self.put_menu("Select Audio/Subtitle for Series", [ - ("English Audio", self.change_tracks_handle, "dubbed"), - ("Japanese Audio w/ English Subtitles", self.change_tracks_handle, "subbed"), - ("Manual by Track Index (Less Reliable)", self.change_tracks_manual_s1), + self.put_menu(_("Select Audio/Subtitle for Series"), [ + (_("English Audio"), self.change_tracks_handle, "dubbed"), + (_("Japanese Audio w/ English Subtitles"), self.change_tracks_handle, "subbed"), + (_("Manual by Track Index (Less Reliable)"), self.change_tracks_manual_s1), ]) def settings_toggle_bool(self): @@ -330,7 +331,7 @@ class OSDMenu(object): self.preferences_menu() def transcode_settings_menu(self): - self.put_menu("Select Default Transcode Profile") + self.put_menu(_("Select Default Transcode Profile")) handle = self.transcode_settings_handle for i, item in enumerate(TRANSCODE_LEVELS): @@ -362,41 +363,41 @@ class OSDMenu(object): self.playerManager.update_subtitle_visuals() def subtitle_color_menu(self): - self.put_menu("Select Subtitle Color", [ + self.put_menu(_("Select Subtitle Color"), [ (name, self.sub_settings_handle, "subtitle_color", color) for name, color in COLOR_LIST ]) def subtitle_size_menu(self): - self.put_menu("Select Subtitle Size", [ + self.put_menu(_("Select Subtitle Size"), [ (name, self.sub_settings_handle, "subtitle_size", size) for name, size in SIZE_LIST ], selected=2) def subtitle_position_menu(self): - self.put_menu("Select Subtitle Position", [ - ("Bottom", self.sub_settings_handle, "subtitle_position", "bottom"), - ("Top", self.sub_settings_handle, "subtitle_position", "top"), - ("Middle", self.sub_settings_handle, "subtitle_position", "middle"), + self.put_menu(_("Select Subtitle Position"), [ + (_("Bottom"), self.sub_settings_handle, "subtitle_position", "bottom"), + (_("Top"), self.sub_settings_handle, "subtitle_position", "top"), + (_("Middle"), self.sub_settings_handle, "subtitle_position", "middle"), ]) def preferences_menu(self): - self.put_menu("Preferences", [ - self.get_settings_toggle("Always Transcode", "always_transcode"), - self.get_settings_toggle("Auto Play", "auto_play"), - ("Remote Transcode Quality: {0:0.1f} Mbps".format(settings.remote_kbps/1000), self.transcode_settings_menu), - ("Subtitle Size: {0}".format(settings.subtitle_size), self.subtitle_size_menu), - ("Subtitle Position: {0}".format(settings.subtitle_position), self.subtitle_position_menu), - ("Subtitle Color: {0}".format(self.get_subtitle_color(settings.subtitle_color)), self.subtitle_color_menu), - self.get_settings_toggle("Transcode H265 to H264", "transcode_h265"), - self.get_settings_toggle("Transcode Hi10p to 8bit", "transcode_hi10p"), - self.get_settings_toggle("Direct Paths", "direct_paths"), - self.get_settings_toggle("Auto Fullscreen", "fullscreen"), - self.get_settings_toggle("Media Key Seek", "media_key_seek"), - self.get_settings_toggle("Use Web Seek Pref", "use_web_seek"), - self.get_settings_toggle("Display Mirroring", "display_mirroring"), - self.get_settings_toggle("Transcode to H265", "transcode_to_h265"), - self.get_settings_toggle("Write Logs to File", "write_logs"), + self.put_menu(_("Preferences"), [ + self.get_settings_toggle(_("Always Transcode"), "always_transcode"), + self.get_settings_toggle(_("Auto Play"), "auto_play"), + (_("Remote Transcode Quality: {0:0.1f} Mbps").format(settings.remote_kbps/1000), self.transcode_settings_menu), + (_("Subtitle Size: {0}").format(settings.subtitle_size), self.subtitle_size_menu), + (_("Subtitle Position: {0}").format(settings.subtitle_position), self.subtitle_position_menu), + (_("Subtitle Color: {0}").format(self.get_subtitle_color(settings.subtitle_color)), self.subtitle_color_menu), + self.get_settings_toggle(_("Transcode H265 to H264"), "transcode_h265"), + self.get_settings_toggle(_("Transcode Hi10p to 8bit"), "transcode_hi10p"), + self.get_settings_toggle(_("Direct Paths"), "direct_paths"), + self.get_settings_toggle(_("Auto Fullscreen"), "fullscreen"), + self.get_settings_toggle(_("Media Key Seek"), "media_key_seek"), + self.get_settings_toggle(_("Use Web Seek Pref"), "use_web_seek"), + self.get_settings_toggle(_("Display Mirroring"), "display_mirroring"), + self.get_settings_toggle(_("Transcode to H265"), "transcode_to_h265"), + self.get_settings_toggle(_("Write Logs to File"), "write_logs"), ]) def unwatched_menu_handle(self): diff --git a/jellyfin_mpv_shim/messages/base.pot b/jellyfin_mpv_shim/messages/base.pot new file mode 100644 index 0000000..cf5cd32 --- /dev/null +++ b/jellyfin_mpv_shim/messages/base.pot @@ -0,0 +1,526 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2020-08-10 02:57+EDT\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: jellyfin_mpv_shim/bulk_subtitle.py:20 +msgid "Selecting Tracks..." +msgstr "" + +#: jellyfin_mpv_shim/bulk_subtitle.py:68 +msgid "{0}: No Subtitles" +msgstr "" + +#: jellyfin_mpv_shim/bulk_subtitle.py:97 +msgid "{0}: Fail" +msgstr "" + +#: jellyfin_mpv_shim/bulk_subtitle.py:100 +msgid "Set Subbed: {0} ok, {1} fail" +msgstr "" + +#: jellyfin_mpv_shim/bulk_subtitle.py:103 +msgid "Set Dubbed: {0} ok, {1} audio only, {2} fail" +msgstr "" + +#: jellyfin_mpv_shim/bulk_subtitle.py:106 +msgid "Manual: {0} ok, {1} fail" +msgstr "" + +#: jellyfin_mpv_shim/bulk_subtitle.py:110 +msgid "Setting Current..." +msgstr "" + +#: jellyfin_mpv_shim/clients.py:45 +msgid "Server URL: " +msgstr "" + +#: jellyfin_mpv_shim/clients.py:46 +msgid "Username: " +msgstr "" + +#: jellyfin_mpv_shim/clients.py:47 +msgid "Password: " +msgstr "" + +#: jellyfin_mpv_shim/clients.py:52 +msgid "Successfully added server." +msgstr "" + +#: jellyfin_mpv_shim/clients.py:53 +msgid "Add another server? [y/N] " +msgstr "" + +#: jellyfin_mpv_shim/clients.py:56 +msgid "Adding server failed." +msgstr "" + +#: jellyfin_mpv_shim/display_mirror/__init__.py:103 +msgid "Ready to cast" +msgstr "" + +#: jellyfin_mpv_shim/display_mirror/__init__.py:104 +msgid "Select your media in Jellyfin and play it here" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:136 +msgid "Application Log" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:211 jellyfin_mpv_shim/gui_mgr.py:291 +msgid "Add Server" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:212 +msgid "" +"Could not add server.\n" +"Please check your connection infomation." +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:228 +msgid "Fail" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:228 +msgid "Ok" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:237 +msgid "Server Configuration" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:260 +msgid "Server:" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:265 +msgid "Username:" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:270 +msgid "Password:" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:293 +msgid "Remove Server" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:295 +msgid "Close" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:399 +msgid "Configure Servers" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:400 +msgid "Show Console" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:401 +msgid "Application Menu" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:402 +msgid "Open Config Folder" +msgstr "" + +#: jellyfin_mpv_shim/gui_mgr.py:403 +msgid "Quit" +msgstr "" + +#: jellyfin_mpv_shim/media.py:105 +msgid " (Transcode)" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:28 +msgid "White" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:29 +msgid "Yellow" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:30 +msgid "Black" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:31 +msgid "Cyan" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:32 +msgid "Blue" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:33 +msgid "Green" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:34 +msgid "Magenta" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:35 +msgid "Red" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:36 +msgid "Gray" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:40 +msgid "Tiny" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:41 +msgid "Small" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:42 +msgid "Normal" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:43 +msgid "Large" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:44 +msgid "Huge" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:105 +msgid "Main Menu" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:110 +msgid "Change Audio" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:111 +msgid "Change Subtitles" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:112 +msgid "Change Video Quality" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:113 jellyfin_mpv_shim/syncplay.py:465 +msgid "SyncPlay" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:117 +msgid "MPV Shim v{0} Release Info/Download" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:121 +msgid "Change Video Playback Profile" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:123 +msgid "Auto Set Audio/Subtitles (Entire Series)" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:124 +msgid "Quit and Mark Unwatched" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:126 +msgid "Screenshot" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:130 +msgid "Video Playback Profiles" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:133 +msgid "SVP Settings" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:136 jellyfin_mpv_shim/menu.py:385 +msgid "Preferences" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:137 +msgid "Close Menu" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:215 +msgid "Select Audio Track" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:236 +msgid "Select Subtitle Track" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:241 +msgid "None" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:267 +msgid "Select Transcode Quality" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:268 +msgid "No Transcode" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:269 +msgid "Maximum" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:304 +msgid "Select Audio/Subtitle for Series" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:305 +msgid "English Audio" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:306 +msgid "Japanese Audio w/ English Subtitles" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:307 +msgid "Manual by Track Index (Less Reliable)" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:334 +msgid "Select Default Transcode Profile" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:366 +msgid "Select Subtitle Color" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:372 +msgid "Select Subtitle Size" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:378 +msgid "Select Subtitle Position" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:379 +msgid "Bottom" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:380 +msgid "Top" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:381 +msgid "Middle" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:386 +msgid "Always Transcode" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:387 +msgid "Auto Play" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:388 +msgid "Remote Transcode Quality: {0:0.1f} Mbps" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:389 +msgid "Subtitle Size: {0}" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:390 +msgid "Subtitle Position: {0}" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:391 +msgid "Subtitle Color: {0}" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:392 +msgid "Transcode H265 to H264" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:393 +msgid "Transcode Hi10p to 8bit" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:394 +msgid "Direct Paths" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:395 +msgid "Auto Fullscreen" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:396 +msgid "Media Key Seek" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:397 +msgid "Use Web Seek Pref" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:398 +msgid "Display Mirroring" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:399 +msgid "Transcode to H265" +msgstr "" + +#: jellyfin_mpv_shim/menu.py:400 +msgid "Write Logs to File" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:36 +#: jellyfin_mpv_shim/svp_integration.py:48 +msgid "Automatic" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:136 +msgid "Disabled" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:144 +msgid "Select SVP Profile" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:147 +msgid "SVP is Not Active" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:148 +msgid "Disable" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:149 +msgid "Retry" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:152 +msgid "SVP is Disabled" +msgstr "" + +#: jellyfin_mpv_shim/svp_integration.py:153 +msgid "Enable SVP" +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:16 +msgid "The specified SyncPlay group does not exist." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:17 +msgid "Creating SyncPlay groups is not allowed." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:18 +msgid "SyncPlay group access was denied." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:19 +msgid "Access to the SyncPlay library was denied." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:128 +msgid "SpeedToSync (x{0})" +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:138 +msgid "Sync Disabled (Too Many Attempts)" +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:146 +msgid "SkipToSync (x{0})" +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:199 +msgid "SyncPlay enabled." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:218 +msgid "SyncPlay disabled." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:252 +msgid "{0} has joined." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:254 +msgid "{0} has left." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:256 +msgid "{0} is buffering." +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:453 jellyfin_mpv_shim/video_profile.py:149 +msgid "None (Disabled)" +msgstr "" + +#: jellyfin_mpv_shim/syncplay.py:457 +msgid "New Group" +msgstr "" + +#: jellyfin_mpv_shim/update_check.py:47 +msgid "" +"MPV Shim v{0} Update Available\n" +"Open menu (press c) for details." +msgstr "" + +#: jellyfin_mpv_shim/utils.py:236 +msgid "Unkn" +msgstr "" + +#: jellyfin_mpv_shim/utils.py:237 +msgid " Forced" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:13 +msgid "Generic (FSRCNNX)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:14 +msgid "Generic High (FSRCNNX x16)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:15 +msgid "Anime4K x4 Faithful (For SD)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:16 +msgid "Anime4K x4 Perceptual (For SD)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:17 +msgid "Anime4K x4 Perceptual + Deblur (For SD)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:18 +msgid "Anime4K x2 Faithful (For HD)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:19 +msgid "Anime4K x2 Perceptual (For HD)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:20 +msgid "Anime4K x2 Perceptual + Deblur (For HD)" +msgstr "" + +#: jellyfin_mpv_shim/video_profile.py:160 +msgid "Select Shader Profile" +msgstr "" + diff --git a/jellyfin_mpv_shim/mpv_shim.py b/jellyfin_mpv_shim/mpv_shim.py index 026c8ab..be5cda5 100755 --- a/jellyfin_mpv_shim/mpv_shim.py +++ b/jellyfin_mpv_shim/mpv_shim.py @@ -7,6 +7,7 @@ import multiprocessing from threading import Event from . import conffile +from . import i18n from .conf import settings from .clients import clientManager from .constants import APP_NAME @@ -20,6 +21,7 @@ logging.getLogger('requests').setLevel(logging.CRITICAL) def main(desktop=False, cef=False): conf_file = conffile.get(APP_NAME, 'conf.json') settings.load(conf_file) + i18n.configure() if settings.sanitize_output: enable_sanitization() diff --git a/jellyfin_mpv_shim/svp_integration.py b/jellyfin_mpv_shim/svp_integration.py index 32e8bf7..737b6bc 100644 --- a/jellyfin_mpv_shim/svp_integration.py +++ b/jellyfin_mpv_shim/svp_integration.py @@ -1,4 +1,6 @@ from .conf import settings +from .i18n import _ + import urllib.request import urllib.error import logging @@ -12,7 +14,7 @@ def list_request(path): response = urllib.request.urlopen(settings.svp_url + "?" + path) return response.read().decode('utf-8').replace('\r\n', '\n').split('\n') except urllib.error.URLError as ex: - log.error("Could not reach SVP API server.", exc_info=1) + log.error("Could not reach SVP API server.", exc_info=True) return None def simple_request(path): @@ -31,7 +33,7 @@ def get_profiles(): if profile_id == "predef": continue if profile_id == "P10000001_1001_1001_1001_100000000001": - profile_name = "Automatic" + profile_name = _("Automatic") else: profile_name = simple_request("profiles.{0}.title".format(profile_id)) if simple_request("profiles.{0}.on".format(profile_id)) == "false": @@ -43,7 +45,7 @@ def get_profiles(): def get_name_from_guid(profile_id): profile_id = "P" + profile_id[1:-1].replace("-", "_") if profile_id == "P10000001_1001_1001_1001_100000000001": - return "Automatic" + return _("Automatic") else: return simple_request("profiles.{0}.title".format(profile_id)) @@ -55,7 +57,7 @@ def is_svp_alive(): response = list_request("") return response is not None except Exception: - log.error("Could not reach SVP API server.", exc_info=1) + log.error("Could not reach SVP API server.", exc_info=True) return False def is_svp_enabled(): @@ -131,7 +133,7 @@ class SVPManager: selected = 0 active_profile = get_last_profile() profile_option_list = [ - ("Disabled", self.menu_set_profile, None) + (_("Disabled"), self.menu_set_profile, None) ] for i, (profile_id, profile_name) in enumerate(get_profiles().items()): profile_option_list.append( @@ -139,14 +141,14 @@ class SVPManager: ) if profile_id == active_profile: selected = i+1 - self.menu.put_menu("Select SVP Profile", profile_option_list, selected) + self.menu.put_menu(_("Select SVP Profile"), profile_option_list, selected) else: if is_svp_enabled(): - self.menu.put_menu("SVP is Not Active", [ - ("Disable", self.menu_set_profile, None), - ("Retry", self.menu_set_enabled) + self.menu.put_menu(_("SVP is Not Active"), [ + (_("Disable"), self.menu_set_profile, None), + (_("Retry"), self.menu_set_enabled) ], selected=1) else: - self.menu.put_menu("SVP is Disabled", [ - ("Enable SVP", self.menu_set_enabled) + self.menu.put_menu(_("SVP is Disabled"), [ + (_("Enable SVP"), self.menu_set_enabled) ]) diff --git a/jellyfin_mpv_shim/syncplay.py b/jellyfin_mpv_shim/syncplay.py index f5e6166..01dba62 100644 --- a/jellyfin_mpv_shim/syncplay.py +++ b/jellyfin_mpv_shim/syncplay.py @@ -3,6 +3,7 @@ import threading import os from datetime import datetime, timedelta from .media import Media +from .i18n import _ from time import sleep # This is based on: https://github.com/jellyfin/jellyfin-web/blob/master/src/components/syncPlay/syncPlayManager.js @@ -12,10 +13,10 @@ from .conf import settings log = logging.getLogger('syncplay') seconds_in_ticks = 10000000 info_commands = { - "GroupDoesNotExist": "The specified SyncPlay group does not exist.", - "CreateGroupDenied": "Creating SyncPlay groups is not allowed.", - "JoinGroupDenied": "SyncPlay group access was denied.", - "LibraryAccessDenied": "Access to the SyncPlay library was denied." + "GroupDoesNotExist": _("The specified SyncPlay group does not exist."), + "CreateGroupDenied": _("Creating SyncPlay groups is not allowed."), + "JoinGroupDenied": _("SyncPlay group access was denied."), + "LibraryAccessDenied": _("Access to the SyncPlay library was denied.") } @@ -124,7 +125,7 @@ class SyncPlayManager: self.sync_enabled = False self.attempts += 1 log.info("SyncPlay Speed to Sync rate: {0}".format(speed)) - self.player_message("SpeedToSync (x{0})".format(speed)) + self.player_message(_("SpeedToSync (x{0})").format(speed)) def callback(): self.player.speed = 1 @@ -134,7 +135,7 @@ class SyncPlayManager: if self.attempts > settings.sync_attempts: self.sync_enabled = False log.info("SyncPlay Sync Disabled due to too many attempts.") - self.player_message("Sync Disabled (Too Many Attempts)") + self.player_message(_("Sync Disabled (Too Many Attempts)")) return # Skip To Sync Method @@ -142,7 +143,7 @@ class SyncPlayManager: self.sync_enabled = False self.attempts += 1 log.info("SyncPlay Skip to Sync Activated") - self.player_message("SkipToSync (x{0})".format(self.attempts)) + self.player_message(_("SkipToSync (x{0})").format(self.attempts)) def callback(): self.sync_enabled = True @@ -195,7 +196,7 @@ class SyncPlayManager: log.info("Syncplay enabled.") if from_server: - self.player_message("SyncPlay enabled.") + self.player_message(_("SyncPlay enabled.")) def disable_sync_play(self, from_server): self.player.speed = self.playback_rate @@ -214,7 +215,7 @@ class SyncPlayManager: log.info("Syncplay disabled.") if from_server: - self.player_message("SyncPlay disabled.") + self.player_message(_("SyncPlay disabled.")) # On Buffer def on_buffer(self): @@ -248,11 +249,11 @@ class SyncPlayManager: elif command_type == "GroupLeft" or command_type == "NotInGroup": self.disable_sync_play(True) elif command_type == "UserJoined": - self.player_message("{0} has joined.".format(command["Data"])) + self.player_message(_("{0} has joined.").format(command["Data"])) elif command_type == "UserLeft": - self.player_message("{0} has left.".format(command["Data"])) + self.player_message(_("{0} has left.").format(command["Data"])) elif command_type == "GroupWait": - self.player_message("{0} is buffering.".format(command["Data"])) + self.player_message(_("{0} is buffering.").format(command["Data"])) else: log.error("Unknown SyncPlay command {0} payload {1}.".format(command_type, command)) @@ -449,11 +450,11 @@ class SyncPlayManager: selected = 0 offset = 1 group_option_list = [ - ("None (Disabled)", self.menu_disable, None), + (_("None (Disabled)"), self.menu_disable, None), ] if not self.is_enabled(): offset = 2 - group_option_list.append(("New Group", self.menu_create_group, None)) + group_option_list.append((_("New Group"), self.menu_create_group, None)) groups = self.client.jellyfin.get_sync_play(self.playerManager._video.item_id) for i, group in enumerate(groups): group_option_list.append( @@ -461,4 +462,4 @@ class SyncPlayManager: ) if group["GroupId"] == self.current_group: selected = i + offset - self.menu.put_menu("SyncPlay", group_option_list, selected) + self.menu.put_menu(_("SyncPlay"), group_option_list, selected) diff --git a/jellyfin_mpv_shim/update_check.py b/jellyfin_mpv_shim/update_check.py index b199086..7533166 100644 --- a/jellyfin_mpv_shim/update_check.py +++ b/jellyfin_mpv_shim/update_check.py @@ -5,6 +5,7 @@ import webbrowser from .constants import CLIENT_VERSION from .conf import settings +from .i18n import _ log = logging.getLogger("update_check") release_url = "https://github.com/iwalton3/jellyfin-mpv-shim/releases/" @@ -43,7 +44,7 @@ class UpdateChecker: self.has_notified = True log.info("Update Available: {0}".format(self.new_version)) self.playerManager._player.show_text( - "MPV Shim v{0} Update Available\nOpen menu (press c) for details.".format(self.new_version), + _("MPV Shim v{0} Update Available\nOpen menu (press c) for details.").format(self.new_version), 5000, 1 ) diff --git a/jellyfin_mpv_shim/utils.py b/jellyfin_mpv_shim/utils.py index 4a87dd6..fc16bff 100644 --- a/jellyfin_mpv_shim/utils.py +++ b/jellyfin_mpv_shim/utils.py @@ -11,6 +11,7 @@ from .conf import settings from datetime import datetime from functools import wraps from .constants import USER_APP_NAME +from .i18n import _ log = logging.getLogger('utils') @@ -232,8 +233,8 @@ def get_profile(is_remote=False, video_bitrate=None, force_transcode=False, is_t def get_sub_display_title(stream): return "{0}{1} ({2})".format( - stream.get("Language", "Unkn").capitalize(), - " Forced" if stream.get("IsForced") else "", + stream.get("Language", _("Unkn")).capitalize(), + _(" Forced") if stream.get("IsForced") else "", stream.get("Codec") ) diff --git a/jellyfin_mpv_shim/video_profile.py b/jellyfin_mpv_shim/video_profile.py index e5d3ec7..d4c22e0 100644 --- a/jellyfin_mpv_shim/video_profile.py +++ b/jellyfin_mpv_shim/video_profile.py @@ -2,12 +2,24 @@ from .conf import settings from . import conffile from .utils import get_resource from .constants import APP_NAME +from .i18n import _ import logging import os.path import shutil import json import time +profile_name_translation = { + "Generic (FSRCNNX)": _("Generic (FSRCNNX)"), + "Generic High (FSRCNNX x16)": _("Generic High (FSRCNNX x16)"), + "Anime4K x4 Faithful (For SD)": _("Anime4K x4 Faithful (For SD)"), + "Anime4K x4 Perceptual (For SD)": _("Anime4K x4 Perceptual (For SD)"), + "Anime4K x4 Perceptual + Deblur (For SD)": _("Anime4K x4 Perceptual + Deblur (For SD)"), + "Anime4K x2 Faithful (For HD)": _("Anime4K x2 Faithful (For HD)"), + "Anime4K x2 Perceptual (For HD)": _("Anime4K x2 Perceptual (For HD)"), + "Anime4K x2 Perceptual + Deblur (For HD)": _("Anime4K x2 Perceptual + Deblur (For HD)") +} + log = logging.getLogger('video_profile') class MPVSettingError(Exception): @@ -54,7 +66,7 @@ class VideoProfileManager: try: self.defaults[key] = getattr(self.playerManager._player, key) except Exception: - log.warning("Your MPV does not support setting {0} used in shader pack.".format(key), exc_info=1) + log.warning("Your MPV does not support setting {0} used in shader pack.".format(key), exc_info=True) if settings.shader_pack_profile is not None: self.load_profile(settings.shader_pack_profile, reset=False) @@ -102,15 +114,15 @@ class VideoProfileManager: self.current_profile = profile_name return True except MPVSettingError as ex: - log.error("Could not apply shader profile.", exc_info=1) + log.error("Could not apply shader profile.", exc_info=True) return False def unload_profile(self): log.info("Unloading shader profile.") self.playerManager._player.glsl_shaders = [] for setting in self.used_settings: + value = self.defaults[setting] try: - value = self.defaults[setting] setattr(self.playerManager._player, setting, value) except Exception: log.warning("Default setting {0} value {1} is invalid.".format(setting, value)) @@ -134,12 +146,15 @@ class VideoProfileManager: def menu_action(self): selected = 0 profile_option_list = [ - ("None (Disabled)", self.menu_handle, None) + (_("None (Disabled)"), self.menu_handle, None) ] for i, (profile_name, profile) in enumerate(self.profiles.items()): + name = profile["displayname"] + if name in profile_name_translation: + name = profile_name_translation[name] profile_option_list.append( - (profile["displayname"], self.menu_handle, profile_name) + (name, self.menu_handle, profile_name) ) if profile_name == self.current_profile: selected = i+1 - self.menu.put_menu("Select Shader Profile", profile_option_list, selected) + self.menu.put_menu(_("Select Shader Profile"), profile_option_list, selected)