diff --git a/EDMC.py b/EDMC.py index 39d98982..64488778 100755 --- a/EDMC.py +++ b/EDMC.py @@ -26,7 +26,6 @@ from EDMCLogging import edmclogger, logger, logging if TYPE_CHECKING: from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy - def _(x: str): return x edmclogger.set_channels_loglevel(logging.INFO) @@ -35,7 +34,7 @@ import collate import commodity import companion import edshipyard -import l10n +from l10n import translations as tr import loadout import outfitting import shipyard @@ -66,7 +65,7 @@ Locale LC_TIME: {locale.getlocale(locale.LC_TIME)}''' ) -l10n.Translations.install_dummy() +tr.install_dummy() SERVER_RETRY = 5 # retry pause for Companion servers [s] EXIT_SUCCESS, EXIT_SERVER, EXIT_CREDENTIALS, EXIT_VERIFICATION, EXIT_LAGGING, EXIT_SYS_ERR, EXIT_ARGS, \ @@ -164,7 +163,7 @@ def main(): # noqa: C901, CCR001 newversion: EDMCVersion | None = updater.check_appcast() if newversion: # LANG: Update Available Text - newverstr: str = _("{NEWVER} is available").format(NEWVER=newversion.title) + newverstr: str = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title) print(f'{appversion()} ({newverstr})') else: print(appversion()) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 3ab97eb5..09e6fc4e 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -413,9 +413,6 @@ if TYPE_CHECKING: from infi.systray import SysTrayIcon # isort: on - def _(x: str) -> str: - """Fake the l10n translation functions for typing.""" - return x import tkinter as tk import tkinter.filedialog @@ -432,7 +429,7 @@ from commodity import COMMODITY_CSV from dashboard import dashboard from edmc_data import ship_name_map from hotkey import hotkeymgr -from l10n import Translations +from l10n import translations as tr from monitor import monitor from theme import theme from ttkHyperlinkLabel import HyperlinkLabel @@ -590,7 +587,7 @@ class AppWindow: self.button = ttk.Button( frame, name='update_button', - text=_('Update'), # LANG: Main UI Update button + text=tr.tl('Update'), # LANG: Main UI Update button width=28, default=tk.ACTIVE, state=tk.DISABLED @@ -660,7 +657,7 @@ class AppWindow: self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) self.system_menu.add_separator() # LANG: Appearance - Label for checkbox to select if application always on top - self.system_menu.add_checkbutton(label=_('Always on top'), + self.system_menu.add_checkbutton(label=tr.tl('Always on top'), variable=self.always_ontop, command=self.ontop_changed) # Appearance setting self.menubar.add_cascade(menu=self.system_menu) @@ -765,7 +762,7 @@ class AppWindow: # Check for Valid Providers validate_providers() if monitor.cmdr is None: - self.status['text'] = _("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game + self.status['text'] = tr.tl("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game # Start a protocol handler to handle cAPI registration. Requires main loop to be running. self.w.after_idle(lambda: protocol.protocolhandler.start(self.w)) @@ -795,7 +792,7 @@ class AppWindow: suit = monitor.state.get('SuitCurrent') if suit is None: - self.suit['text'] = f'<{_("Unknown")}>' # LANG: Unknown suit + self.suit['text'] = f'<{tr.tl("Unknown")}>' # LANG: Unknown suit return suitname = suit['edmcName'] @@ -851,45 +848,45 @@ class AppWindow: # (Re-)install log monitoring if not monitor.start(self.w): # LANG: ED Journal file location appears to be in error - self.status['text'] = _('Error: Check E:D journal file location') + self.status['text'] = tr.tl('Error: Check E:D journal file location') if dologin and monitor.cmdr: self.login() # Login if not already logged in with this Cmdr def set_labels(self): """Set main window labels, e.g. after language change.""" - self.cmdr_label['text'] = _('Cmdr') + ':' # LANG: Label for commander name in main window + self.cmdr_label['text'] = tr.tl('Cmdr') + ':' # LANG: Label for commander name in main window # LANG: 'Ship' or multi-crew role label in main window, as applicable - self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':' # Main window - self.suit_label['text'] = _('Suit') + ':' # LANG: Label for 'Suit' line in main UI - self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI - self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI - self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window - self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title - self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title - self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title - self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title - self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title - self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title + self.ship_label['text'] = (monitor.state['Captain'] and tr.tl('Role') or tr.tl('Ship')) + ':' # Main window + self.suit_label['text'] = tr.tl('Suit') + ':' # LANG: Label for 'Suit' line in main UI + self.system_label['text'] = tr.tl('System') + ':' # LANG: Label for 'System' line in main UI + self.station_label['text'] = tr.tl('Station') + ':' # LANG: Label for 'Station' line in main UI + self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window + self.menubar.entryconfigure(1, label=tr.tl('File')) # LANG: 'File' menu title + self.menubar.entryconfigure(2, label=tr.tl('Edit')) # LANG: 'Edit' menu title + self.menubar.entryconfigure(3, label=tr.tl('Help')) # LANG: 'Help' menu title + self.theme_file_menu['text'] = tr.tl('File') # LANG: 'File' menu title + self.theme_edit_menu['text'] = tr.tl('Edit') # LANG: 'Edit' menu title + self.theme_help_menu['text'] = tr.tl('Help') # LANG: 'Help' menu title # File menu - self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status - self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data... - self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings - self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit + self.file_menu.entryconfigure(0, label=tr.tl('Status')) # LANG: File > Status + self.file_menu.entryconfigure(1, label=tr.tl('Save Raw Data...')) # LANG: File > Save Raw Data... + self.file_menu.entryconfigure(2, label=tr.tl('Settings')) # LANG: File > Settings + self.file_menu.entryconfigure(4, label=tr.tl('Exit')) # LANG: File > Exit # Help menu - self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation - self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting - self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug - self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy - self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes - self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates... - self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App - self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder + self.help_menu.entryconfigure(0, label=tr.tl('Documentation')) # LANG: Help > Documentation + self.help_menu.entryconfigure(1, label=tr.tl('Troubleshooting')) # LANG: Help > Troubleshooting + self.help_menu.entryconfigure(2, label=tr.tl('Report A Bug')) # LANG: Help > Report A Bug + self.help_menu.entryconfigure(3, label=tr.tl('Privacy Policy')) # LANG: Help > Privacy Policy + self.help_menu.entryconfigure(4, label=tr.tl('Release Notes')) # LANG: Help > Release Notes + self.help_menu.entryconfigure(5, label=tr.tl('Check for Updates...')) # LANG: Help > Check for Updates... + self.help_menu.entryconfigure(6, label=tr.tl("About {APP}").format(APP=applongname)) # LANG: Help > About App + self.help_menu.entryconfigure(7, label=tr.tl('Open Log Folder')) # LANG: Help > Open Log Folder # Edit menu - self.edit_menu.entryconfigure(0, label=_('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste' + self.edit_menu.entryconfigure(0, label=tr.tl('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste' def login(self): """Initiate CAPI/Frontier login and set other necessary state.""" @@ -900,12 +897,12 @@ class AppWindow: if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') # LANG: CAPI auth aborted because of killswitch - self.status['text'] = _('CAPI auth disabled by killswitch') + self.status['text'] = tr.tl('CAPI auth disabled by killswitch') return if not self.status['text']: # LANG: Status - Attempting to get a Frontier Auth Access Token - self.status['text'] = _('Logging in...') + self.status['text'] = tr.tl('Logging in...') self.button['state'] = self.theme_button['state'] = tk.DISABLED @@ -916,7 +913,7 @@ class AppWindow: try: if companion.session.login(monitor.cmdr, monitor.is_beta): # LANG: Successfully authenticated with the Frontier website - self.status['text'] = _('Authentication successful') + self.status['text'] = tr.tl('Authentication successful') self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data @@ -947,17 +944,17 @@ class AppWindow: # Signal as error because the user might actually be docked # but the server hosting the Companion API hasn't caught up # LANG: Player is not docked at a station, when we expect them to be - self._handle_status(_("You're not docked at a station!")) + self._handle_status(tr.tl("You're not docked at a station!")) return False # Ignore possibly missing shipyard info if output_flags & config.OUT_EDDN_SEND_STATION_DATA and not (has_commodities or has_modules): # LANG: Status - Either no market or no modules data for station from Frontier CAPI - self._handle_status(_("Station doesn't have anything!")) + self._handle_status(tr.tl("Station doesn't have anything!")) elif not has_commodities: # LANG: Status - No station market data from Frontier CAPI - self._handle_status(_("Station doesn't have a market!")) + self._handle_status(tr.tl("Station doesn't have a market!")) elif output_flags & commodities_flag: # Fixup anomalies in the comodity data @@ -995,7 +992,7 @@ class AppWindow: if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') # LANG: CAPI auth query aborted because of killswitch - self.status['text'] = _('CAPI auth disabled by killswitch') + self.status['text'] = tr.tl('CAPI auth disabled by killswitch') hotkeymgr.play_bad() return @@ -1005,37 +1002,37 @@ class AppWindow: if not monitor.cmdr: logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') # LANG: CAPI queries aborted because Cmdr name is unknown - self.status['text'] = _('CAPI query aborted: Cmdr name unknown') + self.status['text'] = tr.tl('CAPI query aborted: Cmdr name unknown') return if not monitor.mode: logger.trace_if('capi.worker', 'Aborting Query: Game Mode unknown') # LANG: CAPI queries aborted because game mode unknown - self.status['text'] = _('CAPI query aborted: Game mode unknown') + self.status['text'] = tr.tl('CAPI query aborted: Game mode unknown') return if monitor.state['GameVersion'] is None: logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown') # LANG: CAPI queries aborted because GameVersion unknown - self.status['text'] = _('CAPI query aborted: GameVersion unknown') + self.status['text'] = tr.tl('CAPI query aborted: GameVersion unknown') return if not monitor.state['SystemName']: logger.trace_if('capi.worker', 'Aborting Query: Current star system unknown') # LANG: CAPI queries aborted because current star system name unknown - self.status['text'] = _('CAPI query aborted: Current system unknown') + self.status['text'] = tr.tl('CAPI query aborted: Current system unknown') return if monitor.state['Captain']: logger.trace_if('capi.worker', 'Aborting Query: In multi-crew') # LANG: CAPI queries aborted because player is in multi-crew on other Cmdr's ship - self.status['text'] = _('CAPI query aborted: In other-ship multi-crew') + self.status['text'] = tr.tl('CAPI query aborted: In other-ship multi-crew') return if monitor.mode == 'CQC': logger.trace_if('capi.worker', 'Aborting Query: In CQC') # LANG: CAPI queries aborted because player is in CQC (Arena) - self.status['text'] = _('CAPI query aborted: CQC (Arena) detected') + self.status['text'] = tr.tl('CAPI query aborted: CQC (Arena) detected') return if companion.session.state == companion.Session.STATE_AUTH: @@ -1056,7 +1053,7 @@ class AppWindow: hotkeymgr.play_good() # LANG: Status - Attempting to retrieve data from Frontier CAPI - self.status['text'] = _('Fetching data...') + self.status['text'] = tr.tl('Fetching data...') self.button['state'] = self.theme_button['state'] = tk.DISABLED self.w.update_idletasks() @@ -1086,20 +1083,20 @@ class AppWindow: if should_return: logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.') # LANG: CAPI fleetcarrier query aborted because of killswitch - self.status['text'] = _('CAPI fleetcarrier disabled by killswitch') + self.status['text'] = tr.tl('CAPI fleetcarrier disabled by killswitch') hotkeymgr.play_bad() return if not monitor.cmdr: logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') # LANG: CAPI fleetcarrier query aborted because Cmdr name is unknown - self.status['text'] = _('CAPI query aborted: Cmdr name unknown') + self.status['text'] = tr.tl('CAPI query aborted: Cmdr name unknown') return if monitor.state['GameVersion'] is None: logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown') # LANG: CAPI fleetcarrier query aborted because GameVersion unknown - self.status['text'] = _('CAPI query aborted: GameVersion unknown') + self.status['text'] = tr.tl('CAPI query aborted: GameVersion unknown') return if not companion.session.retrying: @@ -1108,7 +1105,7 @@ class AppWindow: return # LANG: Status - Attempting to retrieve data from Frontier CAPI - self.status['text'] = _('Fetching data...') + self.status['text'] = tr.tl('Fetching data...') self.w.update_idletasks() query_time = int(time()) @@ -1151,11 +1148,11 @@ class AppWindow: # Validation if 'name' not in capi_response.capi_data: # LANG: No data was returned for the fleetcarrier from the Frontier CAPI - err = self.status['text'] = _('CAPI: No fleetcarrier data returned') + err = self.status['text'] = tr.tl('CAPI: No fleetcarrier data returned') elif not capi_response.capi_data.get('name', {}).get('callsign'): # LANG: We didn't have the fleetcarrier callsign when we should have - err = self.status['text'] = _("CAPI: Fleetcarrier data incomplete") # Shouldn't happen + err = self.status['text'] = tr.tl("CAPI: Fleetcarrier data incomplete") # Shouldn't happen else: if __debug__: # Recording @@ -1174,24 +1171,24 @@ class AppWindow: elif 'commander' not in capi_response.capi_data: # This can happen with EGS Auth if no commander created yet # LANG: No data was returned for the commander from the Frontier CAPI - err = self.status['text'] = _('CAPI: No commander data returned') + err = self.status['text'] = tr.tl('CAPI: No commander data returned') elif not capi_response.capi_data.get('commander', {}).get('name'): # LANG: We didn't have the commander name when we should have - err = self.status['text'] = _("Who are you?!") # Shouldn't happen + err = self.status['text'] = tr.tl("Who are you?!") # Shouldn't happen elif (not capi_response.capi_data.get('lastSystem', {}).get('name') or (capi_response.capi_data['commander'].get('docked') and not capi_response.capi_data.get('lastStarport', {}).get('name'))): # LANG: We don't know where the commander is, when we should - err = self.status['text'] = _("Where are you?!") # Shouldn't happen + err = self.status['text'] = tr.tl("Where are you?!") # Shouldn't happen elif ( not capi_response.capi_data.get('ship', {}).get('name') or not capi_response.capi_data.get('ship', {}).get('modules') ): # LANG: We don't know what ship the commander is in, when we should - err = self.status['text'] = _("What are you flying?!") # Shouldn't happen + err = self.status['text'] = tr.tl("What are you flying?!") # Shouldn't happen elif monitor.cmdr and capi_response.capi_data['commander']['name'] != monitor.cmdr: # Companion API Commander doesn't match Journal @@ -1318,7 +1315,7 @@ class AppWindow: except companion.ServerConnectionError as comp_err: # LANG: Frontier CAPI server error when fetching data - self.status['text'] = _('Frontier CAPI server error') + self.status['text'] = tr.tl('Frontier CAPI server error') logger.warning(f'Exception while contacting server: {comp_err}') err = self.status['text'] = str(comp_err) play_bad = True @@ -1327,7 +1324,7 @@ class AppWindow: # We need to 'close' the auth else it'll see STATE_OK and think login() isn't needed companion.session.reinit_session() # LANG: Frontier CAPI Access Token expired, trying to get a new one - self.status['text'] = _('CAPI: Refreshing access token...') + self.status['text'] = tr.tl('CAPI: Refreshing access token...') if companion.session.login(): logger.debug('Initial query failed, but login() just worked, trying again...') companion.session.retrying = True @@ -1366,7 +1363,7 @@ class AppWindow: if not err: # not self.status['text']: # no errors # LANG: Time when we last obtained Frontier CAPI data - self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(capi_response.query_time)) + self.status['text'] = strftime(tr.tl('Last updated at %H:%M:%S'), localtime(capi_response.query_time)) if capi_response.play_sound and play_bad: hotkeymgr.play_bad() @@ -1394,9 +1391,9 @@ class AppWindow: return { None: '', 'Idle': '', - 'FighterCon': _('Fighter'), # LANG: Multicrew role - 'FireCon': _('Gunner'), # LANG: Multicrew role - 'FlightCon': _('Helm'), # LANG: Multicrew role + 'FighterCon': tr.tl('Fighter'), # LANG: Multicrew role + 'FireCon': tr.tl('Gunner'), # LANG: Multicrew role + 'FlightCon': tr.tl('Helm'), # LANG: Multicrew role }.get(role, role) if monitor.thread is None: @@ -1419,7 +1416,7 @@ class AppWindow: else: self.cmdr['text'] = f'{monitor.cmdr}' - self.ship_label['text'] = _('Role') + ':' # LANG: Multicrew role label in main window + self.ship_label['text'] = tr.tl('Role') + ':' # LANG: Multicrew role label in main window self.ship.configure(state=tk.NORMAL, text=crewroletext(monitor.state['Role']), url=None) elif monitor.cmdr: @@ -1429,7 +1426,7 @@ class AppWindow: else: self.cmdr['text'] = monitor.cmdr - self.ship_label['text'] = _('Ship') + ':' # LANG: 'Ship' label in main UI + self.ship_label['text'] = tr.tl('Ship') + ':' # LANG: 'Ship' label in main UI # TODO: Show something else when on_foot if monitor.state['ShipName']: @@ -1452,7 +1449,7 @@ class AppWindow: else: self.cmdr['text'] = '' - self.ship_label['text'] = _('Ship') + ':' # LANG: 'Ship' label in main UI + self.ship_label['text'] = tr.tl('Ship') + ':' # LANG: 'Ship' label in main UI self.ship['text'] = '' if monitor.cmdr and monitor.is_beta: @@ -1589,7 +1586,7 @@ class AppWindow: try: companion.session.auth_callback() # LANG: Successfully authenticated with the Frontier website - self.status['text'] = _('Authentication successful') + self.status['text'] = tr.tl('Authentication successful') self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data @@ -1675,10 +1672,10 @@ class AppWindow: # Update button in main window cooldown_time = int(self.capi_query_holdoff_time - time()) # LANG: Cooldown on 'Update' button - self.button['text'] = self.theme_button['text'] = _('cooldown {SS}s').format(SS=cooldown_time) + self.button['text'] = self.theme_button['text'] = tr.tl('cooldown {SS}s').format(SS=cooldown_time) self.w.after(1000, self.cooldown) else: - self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window + self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window self.button['state'] = self.theme_button['state'] = ( monitor.cmdr and monitor.mode and @@ -1743,7 +1740,7 @@ class AppWindow: self.parent = parent # LANG: Help > About App - self.title(_('About {APP}').format(APP=applongname)) + self.title(tr.tl('About {APP}').format(APP=applongname)) if parent.winfo_viewable(): self.transient(parent) @@ -1780,7 +1777,7 @@ class AppWindow: self.appversion_label.config(state=tk.DISABLED, bg=frame.cget("background"), font="TkDefaultFont") self.appversion_label.grid(row=row, column=0, sticky=tk.E) # LANG: Help > Release Notes - self.appversion = HyperlinkLabel(frame, compound=tk.RIGHT, text=_('Release Notes'), + self.appversion = HyperlinkLabel(frame, compound=tk.RIGHT, text=tr.tl('Release Notes'), url='https://github.com/EDCD/EDMarketConnector/releases/tag/Release/' f'{appversion_nobuild()}', underline=True) @@ -1806,7 +1803,7 @@ class AppWindow: ttk.Label(frame).grid(row=row, column=0) # spacer row += 1 # LANG: Generic 'OK' button label - button = ttk.Button(frame, text=_('OK'), command=self.apply) + button = ttk.Button(frame, text=tr.tl('OK'), command=self.apply) button.grid(row=row, column=2, sticky=tk.E) button.bind("", lambda event: self.apply()) self.protocol("WM_DELETE_WINDOW", self._destroy) @@ -1874,7 +1871,7 @@ class AppWindow: # Let the user know we're shutting down. # LANG: The application is shutting down - self.status['text'] = _('Shutting down...') + self.status['text'] = tr.tl('Shutting down...') self.w.update_idletasks() logger.info('Starting shutdown procedures...') @@ -2059,10 +2056,10 @@ def validate_providers(): return # LANG: Popup-text about Reset Providers - popup_text = _(r'One or more of your URL Providers were invalid, and have been reset:\r\n\r\n') + popup_text = tr.tl(r'One or more of your URL Providers were invalid, and have been reset:\r\n\r\n') for provider in reset_providers: # LANG: Text About What Provider Was Reset - popup_text += _(r'{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n') + popup_text += tr.tl(r'{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n') popup_text = popup_text.format( PROVIDER=provider, OLDPROV=reset_providers[provider][0], @@ -2074,7 +2071,7 @@ def validate_providers(): tk.messagebox.showinfo( # LANG: Popup window title for Reset Providers - _('EDMC: Default Providers Reset'), + tr.tl('EDMC: Default Providers Reset'), popup_text ) @@ -2200,7 +2197,7 @@ sys.path: {sys.path}''' # Plain, not via `logger` print(f'{applongname} {appversion()}') - Translations.install(config.get_str('language')) # Can generate errors so wait til log set up + tr.install(config.get_str('language')) # Can generate errors so wait til log set up setup_killswitches(args.killswitches_file) @@ -2235,7 +2232,7 @@ sys.path: {sys.path}''' """Display message about 'broken' plugins that failed to load.""" if plug.PLUGINS_broken: # LANG: Popup-text about 'broken' plugins that failed to load - popup_text = _( + popup_text = tr.tl( "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' " "tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py " r"file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by " @@ -2244,9 +2241,9 @@ sys.path: {sys.path}''' # Substitute in the other words. popup_text = popup_text.format( - PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab - FILE=_('File'), # LANG: 'File' menu - SETTINGS=_('Settings'), # LANG: File > Settings + PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab + FILE=tr.tl('File'), # LANG: 'File' menu + SETTINGS=tr.tl('Settings'), # LANG: File > Settings DISABLED='.disabled' ) # And now we do need these to be actual \r\n @@ -2255,7 +2252,7 @@ sys.path: {sys.path}''' tk.messagebox.showinfo( # LANG: Popup window title for list of 'broken' plugins that failed to load - _('EDMC: Broken Plugins'), + tr.tl('EDMC: Broken Plugins'), popup_text ) @@ -2264,7 +2261,7 @@ sys.path: {sys.path}''' plugins_not_py3_last = config.get_int('plugins_not_py3_last', default=0) if (plugins_not_py3_last + 86400) < int(time()) and plug.PLUGINS_not_py3: # LANG: Popup-text about 'active' plugins without Python 3.x support - popup_text = _( + popup_text = tr.tl( "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the " "list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an " "updated version available, else alert the developer that they need to update the code for " @@ -2274,9 +2271,9 @@ sys.path: {sys.path}''' # Substitute in the other words. popup_text = popup_text.format( - PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab - FILE=_('File'), # LANG: 'File' menu - SETTINGS=_('Settings'), # LANG: File > Settings + PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab + FILE=tr.tl('File'), # LANG: 'File' menu + SETTINGS=tr.tl('Settings'), # LANG: File > Settings DISABLED='.disabled' ) # And now we do need these to be actual \r\n @@ -2285,7 +2282,7 @@ sys.path: {sys.path}''' tk.messagebox.showinfo( # LANG: Popup window title for list of 'enabled' plugins that don't work with Python 3.x - _('EDMC: Plugins Without Python 3.x Support'), + tr.tl('EDMC: Plugins Without Python 3.x Support'), popup_text ) config.set('plugins_not_py3_last', int(time())) @@ -2298,7 +2295,7 @@ sys.path: {sys.path}''' if fdevid_file.is_file(): continue # LANG: Popup-text about missing FDEVID Files - popup_text = _( + popup_text = tr.tl( "FDevID Files not found! Some functionality regarding commodities " r"may be disabled.\r\n\r\n Do you want to open the Wiki page on " "how to set up submodules?" @@ -2309,7 +2306,7 @@ sys.path: {sys.path}''' openwikipage = tk.messagebox.askquestion( # LANG: Popup window title for missing FDEVID files - _('FDevIDs: Missing Commodity Files'), + tr.tl('FDevIDs: Missing Commodity Files'), popup_text ) if openwikipage == "yes": diff --git a/companion.py b/companion.py index d0030748..1b9c693a 100644 --- a/companion.py +++ b/companion.py @@ -37,12 +37,11 @@ from config import config, user_agent from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor +from l10n import translations as tr logger = get_main_logger() if TYPE_CHECKING: - def _(x: str): return x - UserDict = collections.UserDict[str, Any] # indicate to our type checkers what this generic class holds normally else: UserDict = collections.UserDict # Otherwise simply use the actual class @@ -224,7 +223,7 @@ class ServerError(Exception): self.args = args if not args: # LANG: Frontier CAPI didn't respond - self.args = (_("Error: Frontier CAPI didn't respond"),) + self.args = (tr.tl("Error: Frontier CAPI didn't respond"),) class ServerConnectionError(ServerError): @@ -243,7 +242,7 @@ class ServerLagging(Exception): self.args = args if not args: # LANG: Frontier CAPI data doesn't agree with latest Journal game location - self.args = (_('Error: Frontier server is lagging'),) + self.args = (tr.tl('Error: Frontier server is lagging'),) class NoMonitorStation(Exception): @@ -259,7 +258,7 @@ class NoMonitorStation(Exception): self.args = args if not args: # LANG: Commander is docked at an EDO settlement, got out and back in, we forgot the station - self.args = (_("Docked but unknown station: EDO Settlement?"),) + self.args = (tr.tl("Docked but unknown station: EDO Settlement?"),) class CredentialsError(Exception): @@ -269,7 +268,7 @@ class CredentialsError(Exception): self.args = args if not args: # LANG: Generic "something went wrong with Frontier Auth" error - self.args = (_('Error: Invalid Credentials'),) + self.args = (tr.tl('Error: Invalid Credentials'),) class CredentialsRequireRefresh(Exception): @@ -294,7 +293,7 @@ class CmdrError(Exception): self.args = args if not args: # LANG: Frontier CAPI authorisation not for currently game-active commander - self.args = (_('Error: Wrong Cmdr'),) + self.args = (tr.tl('Error: Wrong Cmdr'),) class Auth: @@ -429,7 +428,7 @@ class Auth: '' ) # LANG: Generic error prefix - following text is from Frontier auth service - raise CredentialsError(f'{_("Error")}: {error!r}') + raise CredentialsError(f'{tr.tl("Error")}: {error!r}') r = None try: @@ -472,18 +471,18 @@ class Auth: if (usr := data_decode.get('usr')) is None: logger.error('No "usr" in /decode data') # LANG: Frontier auth, no 'usr' section in returned data - raise CredentialsError(_("Error: Couldn't check token customer_id")) + raise CredentialsError(tr.tl("Error: Couldn't check token customer_id")) if (customer_id := usr.get('customer_id')) is None: logger.error('No "usr"->"customer_id" in /decode data') # LANG: Frontier auth, no 'customer_id' in 'usr' section in returned data - raise CredentialsError(_("Error: Couldn't check token customer_id")) + raise CredentialsError(tr.tl("Error: Couldn't check token customer_id")) # All 'FID' seen in Journals so far have been 'F' # Frontier, Steam and Epic if f'F{customer_id}' != monitor.state.get('FID'): # LANG: Frontier auth customer_id doesn't match game session FID - raise CredentialsError(_("Error: customer_id doesn't match!")) + raise CredentialsError(tr.tl("Error: customer_id doesn't match!")) logger.info(f'Frontier CAPI Auth: New token for \"{self.cmdr}\"') cmdrs = config.get_list('cmdrs', default=[]) @@ -505,7 +504,7 @@ class Auth: self.dump(r) # LANG: Failed to get Access Token from Frontier Auth service - raise CredentialsError(_('Error: unable to get token')) from e + raise CredentialsError(tr.tl('Error: unable to get token')) from e logger.error(f"Frontier CAPI Auth: Can't get token for \"{self.cmdr}\"") self.dump(r) @@ -514,7 +513,7 @@ class Auth: '' ) # LANG: Generic error prefix - following text is from Frontier auth service - raise CredentialsError(f'{_("Error")}: {error!r}') + raise CredentialsError(f'{tr.tl("Error")}: {error!r}') @staticmethod def invalidate(cmdr: str | None) -> None: @@ -841,7 +840,7 @@ class Session: except Exception as e: logger.debug('Attempting GET', exc_info=e) # LANG: Frontier CAPI data retrieval failed - raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e + raise ServerError(f'{tr.tl("Frontier CAPI query failure")}: {capi_endpoint}') from e if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data: logger.error('No commander in returned data') @@ -874,7 +873,7 @@ class Session: if response.status_code == 418: # "I'm a teapot" - used to signal maintenance # LANG: Frontier CAPI returned 418, meaning down for maintenance - raise ServerError(_("Frontier CAPI down for maintenance")) + raise ServerError(tr.tl("Frontier CAPI down for maintenance")) logger.exception('Frontier CAPI: Misc. Error') raise ServerError('Frontier CAPI: Misc. Error') diff --git a/journal_lock.py b/journal_lock.py index 3a4dad52..2aae20a6 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -13,17 +13,13 @@ import tkinter as tk from enum import Enum from os import getpid as os_getpid from tkinter import ttk -from typing import TYPE_CHECKING, Callable - +from typing import Callable +from l10n import translations as tr from config import config from EDMCLogging import get_main_logger logger = get_main_logger() -if TYPE_CHECKING: # pragma: no cover - def _(x: str) -> str: - return x - class JournalLockResult(Enum): """Enumeration of possible outcomes of trying to lock the Journal Directory.""" @@ -212,7 +208,7 @@ class JournalLock: self.parent = parent self.callback = callback # LANG: Title text on popup when Journal directory already locked - self.title(_('Journal directory already locked')) + self.title(tr.tl('Journal directory already locked')) # remove decoration if sys.platform == 'win32': @@ -225,16 +221,17 @@ class JournalLock: self.blurb = tk.Label(frame) # LANG: Text for when newly selected Journal directory is already locked - self.blurb['text'] = _("The new Journal Directory location is already locked.{CR}" - "You can either attempt to resolve this and then Retry, or choose to Ignore this.") + self.blurb['text'] = tr.tl("The new Journal Directory location is already locked.{CR}" + "You can either attempt to resolve this and then Retry, " + "or choose to Ignore this.") self.blurb.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW) # LANG: Generic 'Retry' button label - self.retry_button = ttk.Button(frame, text=_('Retry'), command=self.retry) + self.retry_button = ttk.Button(frame, text=tr.tl('Retry'), command=self.retry) self.retry_button.grid(row=2, column=0, sticky=tk.EW) # LANG: Generic 'Ignore' button label - self.ignore_button = ttk.Button(frame, text=_('Ignore'), command=self.ignore) + self.ignore_button = ttk.Button(frame, text=tr.tl('Ignore'), command=self.ignore) self.ignore_button.grid(row=2, column=1, sticky=tk.EW) self.protocol("WM_DELETE_WINDOW", self._destroy) diff --git a/l10n.py b/l10n.py index 183d902a..f3215062 100755 --- a/l10n.py +++ b/l10n.py @@ -23,9 +23,6 @@ from typing import TYPE_CHECKING, Iterable, TextIO, cast from config import config from EDMCLogging import get_main_logger -if TYPE_CHECKING: - def _(x: str) -> str: return x - # Note that this is also done in EDMarketConnector.py, and thus removing this here may not have a desired effect try: locale.setlocale(locale.LC_ALL, '') @@ -61,7 +58,16 @@ if sys.platform == 'win32': GetNumberFormatEx.restype = ctypes.c_int -class _Translations: +class Translations: + """ + The Translation System. + + Contains all the logic needed to support multiple languages in EDMC. + DO NOT USE THIS DIRECTLY UNLESS YOU KNOW WHAT YOU'RE DOING. + In most cases, you'll want to import translations. + + For most cases: from l10n import translations as tr. + """ FALLBACK = 'en' # strings in this code are in English FALLBACK_NAME = 'English' @@ -79,6 +85,8 @@ class _Translations: Use when translation is not desired or not available """ self.translations = {None: {}} + # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. + # Migrate to calling Translations.translate directly. builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n') def install(self, lang: str | None = None) -> None: # noqa: CCR001 @@ -88,7 +96,7 @@ class _Translations: :param lang: The language to translate to, defaults to the preferred language """ available = self.available() - available.add(_Translations.FALLBACK) + available.add(Translations.FALLBACK) if not lang: # Choose the default language for preferred in Locale.preferred_languages(): @@ -122,6 +130,8 @@ class _Translations: except Exception: logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}') + # WARNING: '_' is Deprecated. Will be removed in 6.0 or later. + # Migrate to calling Translations.translate directly. builtins.__dict__['_'] = self.translate def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: @@ -135,12 +145,12 @@ class _Translations: for line in h: if line.strip(): - match = _Translations.TRANS_RE.match(line) + match = Translations.TRANS_RE.match(line) if match: to_set = match.group(2).replace(r'\"', '"').replace('{CR}', '\n') translations[match.group(1).replace(r'\"', '"')] = to_set - elif not _Translations.COMMENT_RE.match(line): + elif not Translations.COMMENT_RE.match(line): logger.debug(f'Bad translation: {line.strip()}') h.close() @@ -149,6 +159,10 @@ class _Translations: return translations + def tl(self, x: str, context: str | None = None) -> str: + """Use the shorthand Dummy loader for the translate function.""" + return self.translate(x, context) + def translate(self, x: str, context: str | None = None) -> str: """ Translate the given string to the current lang. @@ -182,11 +196,11 @@ class _Translations: """Available language names by code.""" names: dict[str | None, str] = { # LANG: The system default language choice in Settings > Appearance - None: _('Default'), # Appearance theme and language setting + None: self.tl('Default'), # Appearance theme and language setting } names.update(sorted( [(lang, self.contents(lang).get(LANGUAGE_ID, lang)) for lang in self.available()] + - [(_Translations.FALLBACK, _Translations.FALLBACK_NAME)], + [(Translations.FALLBACK, Translations.FALLBACK_NAME)], key=lambda x: x[1] )) # Sort by name @@ -324,9 +338,22 @@ class _Locale: # singletons Locale = _Locale() -Translations = _Translations() +translations = Translations() +# WARNING: 'Translations' singleton is deprecated. Will be removed in 6.0 or later. +# Migrate to importing 'translations'. +# Begin Deprecation Zone +class _Translations(Translations): + def __init__(self): + logger.warning(DeprecationWarning('Translations and _Translations() are deprecated. ' + 'Please use translations and Translations() instead.')) + super().__init__() + + +Translations: Translations = _Translations() # type: ignore # Yes, I know this is awful. But we need it for compat. +# End Deprecation Zone + # generate template strings file - like xgettext # parsing is limited - only single ' or " delimited strings, and only one string per line if __name__ == "__main__": diff --git a/monitor.py b/monitor.py index 02b7f91c..f93473ba 100644 --- a/monitor.py +++ b/monitor.py @@ -34,10 +34,6 @@ STARTUP = 'journal.startup' MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds -if TYPE_CHECKING: - def _(x: str) -> str: - return x - if sys.platform == 'win32': import ctypes from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR diff --git a/plugins/coriolis.py b/plugins/coriolis.py index 07b4ac9f..a9eabced 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -27,15 +27,11 @@ import io import json import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING import myNotebook as nb # noqa: N813 # its not my fault. from EDMCLogging import get_main_logger from plug import show_error from config import config - -if TYPE_CHECKING: - def _(s: str) -> str: - ... +from l10n import translations as tr class CoriolisConfig: @@ -45,15 +41,15 @@ class CoriolisConfig: self.normal_url = '' self.beta_url = '' self.override_mode = '' - self.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto - self.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal - self.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta + self.override_text_old_auto = tr.tl('Auto') # LANG: Coriolis normal/beta selection - auto + self.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal + self.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta self.normal_textvar = tk.StringVar() self.beta_textvar = tk.StringVar() self.override_textvar = tk.StringVar() - def initialize_urls(self): + def initialize_urls(self) -> None: """Initialize Coriolis URLs and override mode from configuration.""" self.normal_url = config.get_str('coriolis_normal_url', default=DEFAULT_NORMAL_URL) self.beta_url = config.get_str('coriolis_beta_url', default=DEFAULT_BETA_URL) @@ -63,10 +59,10 @@ class CoriolisConfig: self.beta_textvar.set(value=self.beta_url) self.override_textvar.set( value={ - 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection - 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection - 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection - }.get(self.override_mode, _('Auto')) # LANG: 'Auto' label for Coriolis site override selection + 'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection + 'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection + 'beta': tr.tl('Beta') # LANG: 'Beta' label for Coriolis site override selection + }.get(self.override_mode, tr.tl('Auto')) # LANG: 'Auto' label for Coriolis site override selection ) @@ -91,38 +87,38 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr BOXY = 2 # noqa: N806 # box spacing # Save the old text values for the override mode, so we can update them if the language is changed - coriolis_config.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto - coriolis_config.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal - coriolis_config.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta + coriolis_config.override_text_old_auto = tr.tl('Auto') # LANG: Coriolis normal/beta selection - auto + coriolis_config.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal + coriolis_config.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta conf_frame = nb.Frame(parent) conf_frame.columnconfigure(index=1, weight=1) cur_row = 0 # LANG: Settings>Coriolis: Help/hint for changing coriolis URLs - nb.Label(conf_frame, text=_( + nb.Label(conf_frame, text=tr.tl( "Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" )).grid(sticky=tk.EW, row=cur_row, column=0, padx=PADX, pady=PADY, columnspan=3) cur_row += 1 # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL - nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) + nb.Label(conf_frame, text=tr.tl('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=_("Reset"), + nb.Button(conf_frame, text=tr.tl("Reset"), command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) cur_row += 1 # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL - nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) + nb.Label(conf_frame, text=tr.tl('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY) nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid( sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY ) # LANG: Generic 'Reset' button label - nb.Button(conf_frame, text=_('Reset'), + nb.Button(conf_frame, text=tr.tl('Reset'), command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid( sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 ) @@ -130,16 +126,16 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr # TODO: This needs a help/hint text to be sure users know what it's for. # LANG: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL - nb.Label(conf_frame, text=_('Override Beta/Normal Selection')).grid( + nb.Label(conf_frame, text=tr.tl('Override Beta/Normal Selection')).grid( sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY ) nb.OptionMenu( conf_frame, coriolis_config.override_textvar, coriolis_config.override_textvar.get(), - _('Normal'), # LANG: 'Normal' label for Coriolis site override selection - _('Beta'), # LANG: 'Beta' label for Coriolis site override selection - _('Auto') # LANG: 'Auto' label for Coriolis site override selection + tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection + tr.tl('Beta'), # LANG: 'Beta' label for Coriolis site override selection + tr.tl('Auto') # LANG: 'Auto' label for Coriolis site override selection ).grid(sticky=tk.W, row=cur_row, column=1, padx=PADX, pady=BOXY) cur_row += 1 @@ -159,9 +155,9 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: # Convert to unlocalised names coriolis_config.override_mode = { - _('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal - _('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta - _('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto + tr.tl('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal + tr.tl('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta + tr.tl('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto }.get(coriolis_config.override_mode, coriolis_config.override_mode) # Check if the language was changed and the override_mode was valid before the change @@ -175,18 +171,19 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: if coriolis_config.override_mode in ('beta', 'normal', 'auto'): coriolis_config.override_textvar.set( value={ - 'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection - 'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection - 'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection + 'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection + 'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection + 'beta': tr.tl('Beta') # LANG: 'Beta' label for Coriolis site override selection # LANG: 'Auto' label for Coriolis site override selection - }.get(coriolis_config.override_mode, _('Auto')) + }.get(coriolis_config.override_mode, tr.tl('Auto')) ) # If the override mode is still invalid, default to auto if coriolis_config.override_mode not in ('beta', 'normal', 'auto'): logger.warning(f'Unexpected value {coriolis_config.override_mode=!r}. Defaulting to "auto"') coriolis_config.override_mode = 'auto' - coriolis_config.override_textvar.set(value=_('Auto')) # LANG: 'Auto' label for Coriolis site override selection + # LANG: 'Auto' label for Coriolis site override selection + coriolis_config.override_textvar.set(value=tr.tl('Auto')) config.set('coriolis_normal_url', coriolis_config.normal_url) config.set('coriolis_beta_url', coriolis_config.beta_url) @@ -196,7 +193,7 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None: def _get_target_url(is_beta: bool) -> str: if coriolis_config.override_mode not in ('auto', 'normal', 'beta'): # LANG: Settings>Coriolis - invalid override mode found - show_error(_('Invalid Coriolis override mode!')) + show_error(tr.tl('Invalid Coriolis override mode!')) logger.warning(f'Unexpected override mode {coriolis_config.override_mode!r}! defaulting to auto!') coriolis_config.override_mode = 'auto' if coriolis_config.override_mode == 'beta': diff --git a/plugins/eddn.py b/plugins/eddn.py index 9504d1ab..2b223036 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -31,13 +31,7 @@ import tkinter as tk from platform import system from textwrap import dedent from threading import Lock -from typing import ( - TYPE_CHECKING, - Any, - Iterator, - Mapping, - MutableMapping, -) +from typing import Any, Iterator, Mapping, MutableMapping import requests import companion import edmc_data @@ -52,10 +46,7 @@ from myNotebook import Frame from prefs import prefsVersion from ttkHyperlinkLabel import HyperlinkLabel from util import text - -if TYPE_CHECKING: - def _(x: str) -> str: - return x +from l10n import translations as tr logger = get_main_logger() @@ -441,7 +432,7 @@ class EDDNSender: except requests.exceptions.RequestException as e: logger.debug('Failed sending', exc_info=e) # LANG: Error while trying to send data to EDDN - self.set_ui_status(_("Error: Can't connect to EDDN")) + self.set_ui_status(tr.tl("Error: Can't connect to EDDN")) except Exception as e: logger.debug('Failed sending', exc_info=e) @@ -566,17 +557,17 @@ class EDDNSender: if status_code == 429: # HTTP UPGRADE REQUIRED logger.warning('EDMC is sending schemas that are too old') # LANG: EDDN has banned this version of our client - return _('EDDN Error: EDMC is too old for EDDN. Please update.') + return tr.tl('EDDN Error: EDMC is too old for EDDN. Please update.') if status_code == 400: # we a validation check or something else. logger.warning(f'EDDN Error: {status_code} -- {exception.response}') # LANG: EDDN returned an error that indicates something about what we sent it was wrong - return _('EDDN Error: Validation Failed (EDMC Too Old?). See Log') + return tr.tl('EDDN Error: Validation Failed (EDMC Too Old?). See Log') logger.warning(f'Unknown status code from EDDN: {status_code} -- {exception.response}') # LANG: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number - return _('EDDN Error: Returned {STATUS} status code').format(STATUS=status_code) + return tr.tl('EDDN Error: Returned {STATUS} status code').format(STATUS=status_code) # TODO: a good few of these methods are static or could be classmethods. they should be created as such. @@ -2189,7 +2180,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: this.eddn_station_button = nb.Checkbutton( eddnframe, # LANG: Enable EDDN support for station data checkbox label - text=_('Send station data to the Elite Dangerous Data Network'), + text=tr.tl('Send station data to the Elite Dangerous Data Network'), variable=this.eddn_station, command=prefsvarchanged ) # Output setting @@ -2201,7 +2192,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: this.eddn_system_button = nb.Checkbutton( eddnframe, # LANG: Enable EDDN support for system and other scan data checkbox label - text=_('Send system and scan data to the Elite Dangerous Data Network'), + text=tr.tl('Send system and scan data to the Elite Dangerous Data Network'), variable=this.eddn_system, command=prefsvarchanged ) @@ -2213,7 +2204,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: this.eddn_delay_button = nb.Checkbutton( eddnframe, # LANG: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this - text=_('Delay sending until docked'), + text=tr.tl('Delay sending until docked'), variable=this.eddn_delay ) this.eddn_delay_button.grid(row=cur_row, padx=BUTTONX, pady=PADY, sticky=tk.W) @@ -2328,7 +2319,7 @@ def journal_entry( # noqa: C901, CCR001 """ should_return, new_data = killswitch.check_killswitch('plugins.eddn.journal', entry) if should_return: - plug.show_error(_('EDDN journal handler disabled. See Log.')) # LANG: Killswitch disabled EDDN + plug.show_error(tr.tl('EDDN journal handler disabled. See Log.')) # LANG: Killswitch disabled EDDN return None should_return, new_data = killswitch.check_killswitch(f'plugins.eddn.journal.event.{entry["event"]}', new_data) @@ -2530,7 +2521,7 @@ def journal_entry( # noqa: C901, CCR001 except requests.exceptions.RequestException as e: logger.debug('Failed in send_message', exc_info=e) - return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN + return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN except Exception as e: logger.debug('Failed in export_journal_generic', exc_info=e) @@ -2568,7 +2559,7 @@ def journal_entry( # noqa: C901, CCR001 except requests.exceptions.RequestException as e: logger.debug(f'Failed exporting {entry["event"]}', exc_info=e) - return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN + return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN except Exception as e: logger.debug(f'Failed exporting {entry["event"]}', exc_info=e) @@ -2622,7 +2613,8 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001 status = this.parent.nametowidget(f".{appname.lower()}.status") old_status = status['text'] if not old_status: - status['text'] = _('Sending data to EDDN...') # LANG: Status text shown while attempting to send data + # LANG: Status text shown while attempting to send data + status['text'] = tr.tl('Sending data to EDDN...') status.update_idletasks() this.eddn.export_commodities(data, is_beta) @@ -2634,7 +2626,7 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001 except requests.RequestException as e: logger.debug('Failed exporting data', exc_info=e) - return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN + return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN except Exception as e: logger.debug('Failed exporting data', exc_info=e) diff --git a/plugins/edsm.py b/plugins/edsm.py index b2a8035f..5cd974d5 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -28,7 +28,7 @@ from queue import Queue from threading import Thread from time import sleep from tkinter import ttk -from typing import TYPE_CHECKING, Any, Literal, Mapping, MutableMapping, cast, Sequence +from typing import Any, Literal, Mapping, MutableMapping, cast, Sequence import requests import killswitch import monitor @@ -39,10 +39,8 @@ from config import applongname, appname, appversion, config, debug_senders, user from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel +from l10n import translations as tr -if TYPE_CHECKING: - def _(x: str) -> str: - return x # TODO: # 1) Re-factor EDSM API calls out of journal_entry() into own function. @@ -313,7 +311,8 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr this.log = tk.IntVar(value=config.get_int('edsm_out') and 1) this.log_button = nb.Checkbutton( frame, - text=_('Send flight log and CMDR status to EDSM'), # LANG: Settings>EDSM - Label on checkbox for 'send data' + # LANG: Settings>EDSM - Label on checkbox for 'send data' + text=tr.tl('Send flight log and CMDR status to EDSM'), variable=this.log, command=prefsvarchanged ) @@ -328,7 +327,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr this.label = HyperlinkLabel( frame, - text=_('Elite Dangerous Star Map credentials'), # LANG: Elite Dangerous Star Map credentials + text=tr.tl('Elite Dangerous Star Map credentials'), # LANG: Elite Dangerous Star Map credentials background=nb.Label().cget('background'), url='https://www.edsm.net/settings/api', underline=True @@ -336,21 +335,21 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr if this.label: this.label.grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W) cur_row += 1 - this.cmdr_label = nb.Label(frame, text=_('Cmdr')) # LANG: Game Commander name label in EDSM settings + this.cmdr_label = nb.Label(frame, text=tr.tl('Cmdr')) # LANG: Game Commander name label in EDSM settings this.cmdr_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.cmdr_text = nb.Label(frame) this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.W) cur_row += 1 # LANG: EDSM Commander name label in EDSM settings - this.user_label = nb.Label(frame, text=_('Commander Name')) + this.user_label = nb.Label(frame, text=tr.tl('Commander Name')) this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.user = nb.EntryMenu(frame) this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) cur_row += 1 # LANG: EDSM API key label - this.apikey_label = nb.Label(frame, text=_('API Key')) + this.apikey_label = nb.Label(frame, text=tr.tl('API Key')) this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) @@ -362,7 +361,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr show_password_checkbox = nb.Checkbutton( frame, - text=_('Show API Key'), # LANG: Text EDSM Show API Key + text=tr.tl('Show API Key'), # LANG: Text EDSM Show API Key variable=show_password_var, command=toggle_password_visibility ) @@ -398,7 +397,7 @@ def prefs_cmdr_changed(cmdr: str | None, is_beta: bool) -> None: # noqa: CCR001 else: if this.cmdr_text: # LANG: We have no data on the current commander - this.cmdr_text['text'] = _('None') + this.cmdr_text['text'] = tr.tl('None') to_set: Literal['normal'] | Literal['disabled'] = tk.DISABLED if cmdr and not is_beta and this.log and this.log.get(): @@ -519,7 +518,7 @@ def journal_entry( # noqa: C901, CCR001 should_return, new_entry = killswitch.check_killswitch('plugins.edsm.journal', entry, logger) if should_return: # LANG: EDSM plugin - Journal handling disabled by killswitch - plug.show_error(_('EDSM Handler disabled. See Log.')) + plug.show_error(tr.tl('EDSM Handler disabled. See Log.')) return '' should_return, new_entry = killswitch.check_killswitch( @@ -606,7 +605,7 @@ entry: {entry!r}''' # LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data logger.info("EDSM only accepts Live galaxy data") this.legacy_galaxy_last_notified = datetime.now(timezone.utc) - return _("EDSM only accepts Live galaxy data") # LANG: EDSM - Only Live data + return tr.tl("EDSM only accepts Live galaxy data") # LANG: EDSM - Only Live data return '' @@ -778,7 +777,7 @@ def send_to_edsm( # noqa: CCR001 if msg_num // 100 == 2: logger.warning(f'EDSM\t{msg_num} {msg}\t{json.dumps(pending, separators=(",", ": "))}') # LANG: EDSM Plugin - Error message from EDSM API - plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg)) + plug.show_error(tr.tl('Error: EDSM {MSG}').format(MSG=msg)) else: if msg_num // 100 == 1: logger.trace_if('plugin.edsm.api', 'Overall OK') @@ -944,7 +943,7 @@ def worker() -> None: # noqa: CCR001 C901 else: # LANG: EDSM Plugin - Error connecting to EDSM API - plug.show_error(_("Error: Can't connect to EDSM")) + plug.show_error(tr.tl("Error: Can't connect to EDSM")) if entry['event'].lower() in ('shutdown', 'commander', 'fileheader'): # Game shutdown or new login, so we MUST not hang on to pending pending = [] @@ -1018,11 +1017,11 @@ def edsm_notify_system(reply: Mapping[str, Any]) -> None: if not reply: this.system_link['image'] = this._IMG_ERROR # LANG: EDSM Plugin - Error connecting to EDSM API - plug.show_error(_("Error: Can't connect to EDSM")) + plug.show_error(tr.tl("Error: Can't connect to EDSM")) elif reply['msgnum'] // 100 not in (1, 4): this.system_link['image'] = this._IMG_ERROR # LANG: EDSM Plugin - Error message from EDSM API - plug.show_error(_('Error: EDSM {MSG}').format(MSG=reply['msg'])) + plug.show_error(tr.tl('Error: EDSM {MSG}').format(MSG=reply['msg'])) elif reply.get('systemCreated'): this.system_link['image'] = this._IMG_NEW else: diff --git a/plugins/inara.py b/plugins/inara.py index 810f7523..7c565ab3 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -30,7 +30,7 @@ from datetime import datetime, timedelta, timezone from operator import itemgetter from threading import Lock, Thread from tkinter import ttk -from typing import TYPE_CHECKING, Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union +from typing import Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union import requests import edmc_data import killswitch @@ -42,13 +42,10 @@ from config import applongname, appname, appversion, config, debug_senders from EDMCLogging import get_main_logger from monitor import monitor from ttkHyperlinkLabel import HyperlinkLabel +from l10n import translations as tr logger = get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: - return x - _TIMEOUT = 20 FAKE = ('CQC', 'Training', 'Destination') # Fake systems that shouldn't be sent to Inara @@ -264,7 +261,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: this.log = tk.IntVar(value=config.get_int('inara_out') and 1) this.log_button = nb.Checkbutton( frame, - text=_('Send flight log and Cmdr status to Inara'), # LANG: Checkbox to enable INARA API Usage + text=tr.tl('Send flight log and Cmdr status to Inara'), # LANG: Checkbox to enable INARA API Usage variable=this.log, command=prefsvarchanged ) @@ -280,7 +277,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: # Section heading in settings this.label = HyperlinkLabel( frame, - text=_('Inara credentials'), # LANG: Text for INARA API keys link ( goes to https://inara.cz/settings-api ) + text=tr.tl('Inara credentials'), # LANG: Text for INARA API keys link ( goes to https://inara.cz/settings-api ) background=nb.Label().cget('background'), url='https://inara.cz/settings-api', underline=True @@ -290,7 +287,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: cur_row += 1 # LANG: Inara API key label - this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting + this.apikey_label = nb.Label(frame, text=tr.tl('API Key')) # Inara setting this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W) this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) @@ -301,7 +298,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame: show_password_var.set(False) # Password is initially masked show_password_checkbox = nb.Checkbutton( frame, - text=_('Show API Key'), # LANG: Text Inara Show API key + text=tr.tl('Show API Key'), # LANG: Text Inara Show API key variable=show_password_var, command=toggle_password_visibility, ) @@ -407,7 +404,7 @@ def journal_entry( # noqa: C901, CCR001 should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger) if should_return: - plug.show_error(_('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch + plug.show_error(tr.tl('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch logger.trace('returning due to killswitch match') return '' @@ -432,9 +429,9 @@ def journal_entry( # noqa: C901, CCR001 and config.get_int('inara_out') and not (is_beta or this.multicrew or credentials(cmdr)) ): # LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data - logger.info(_("Inara only accepts Live galaxy data")) + logger.info(tr.tl("Inara only accepts Live galaxy data")) this.legacy_galaxy_last_notified = datetime.now(timezone.utc) - return _("Inara only accepts Live galaxy data") # LANG: Inara - Only Live data + return tr.tl("Inara only accepts Live galaxy data") # LANG: Inara - Only Live data return '' @@ -1642,7 +1639,7 @@ def handle_api_error(data: Mapping[str, Any], status: int, reply: dict[str, Any] logger.warning(f'Inara\t{status} {error_message}') logger.debug(f'JSON data:\n{json.dumps(data, indent=2, separators = (",", ": "))}') # LANG: INARA API returned some kind of error (error message will be contained in {MSG}) - plug.show_error(_('Error: Inara {MSG}').format(MSG=error_message)) + plug.show_error(tr.tl('Error: Inara {MSG}').format(MSG=error_message)) def handle_success_reply(data: Mapping[str, Any], reply: dict[str, Any]) -> None: @@ -1675,7 +1672,7 @@ def handle_individual_error(data_event: dict[str, Any], reply_status: int, reply if reply_status // 100 != 2: # LANG: INARA API returned some kind of error (error message will be contained in {MSG}) - plug.show_error(_('Error: Inara {MSG}').format( + plug.show_error(tr.tl('Error: Inara {MSG}').format( MSG=f'{data_event["eventName"]}, {reply_text}' )) diff --git a/prefs.py b/prefs.py index 285ef0d7..2d4c8d51 100644 --- a/prefs.py +++ b/prefs.py @@ -14,23 +14,19 @@ from os.path import expanduser, expandvars, join, normpath from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812 from tkinter import ttk from types import TracebackType -from typing import TYPE_CHECKING, Any, Callable, Optional, Type - +from typing import Any, Callable, Optional, Type import myNotebook as nb # noqa: N813 import plug from config import appversion_nobuild, config from EDMCLogging import edmclogger, get_main_logger from constants import appname from hotkey import hotkeymgr -from l10n import Translations +from l10n import translations as tr from monitor import monitor from theme import theme from ttkHyperlinkLabel import HyperlinkLabel logger = get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: - return x # TODO: Decouple this from platform as far as possible @@ -224,7 +220,7 @@ class PreferencesDialog(tk.Toplevel): self.parent = parent self.callback = callback # LANG: File > Settings (macOS) - self.title(_('Settings')) + self.title(tr.tl('Settings')) if parent.winfo_viewable(): self.transient(parent) @@ -270,7 +266,7 @@ class PreferencesDialog(tk.Toplevel): buttonframe.columnconfigure(0, weight=1) ttk.Label(buttonframe).grid(row=0, column=0) # spacer # LANG: 'OK' button on Settings/Preferences window - button = ttk.Button(buttonframe, text=_('OK'), command=self.apply) + button = ttk.Button(buttonframe, text=tr.tl('OK'), command=self.apply) button.grid(row=0, column=1, sticky=tk.E) button.bind("", lambda event: self.apply()) self.protocol("WM_DELETE_WINDOW", self._destroy) @@ -313,13 +309,13 @@ class PreferencesDialog(tk.Toplevel): row = AutoInc(start=0) # LANG: Settings > Output - choosing what data to save to files - self.out_label = nb.Label(output_frame, text=_('Please choose what data to save')) + self.out_label = nb.Label(output_frame, text=tr.tl('Please choose what data to save')) self.out_label.grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) self.out_csv = tk.IntVar(value=1 if (output & config.OUT_MKT_CSV) else 0) self.out_csv_button = nb.Checkbutton( output_frame, - text=_('Market data in CSV format file'), # LANG: Settings > Output option + text=tr.tl('Market data in CSV format file'), # LANG: Settings > Output option variable=self.out_csv, command=self.outvarchanged ) @@ -328,7 +324,7 @@ class PreferencesDialog(tk.Toplevel): self.out_td = tk.IntVar(value=1 if (output & config.OUT_MKT_TD) else 0) self.out_td_button = nb.Checkbutton( output_frame, - text=_('Market data in Trade Dangerous format file'), # LANG: Settings > Output option + text=tr.tl('Market data in Trade Dangerous format file'), # LANG: Settings > Output option variable=self.out_td, command=self.outvarchanged ) @@ -338,7 +334,7 @@ class PreferencesDialog(tk.Toplevel): # Output setting self.out_ship_button = nb.Checkbutton( output_frame, - text=_('Ship loadout'), # LANG: Settings > Output option + text=tr.tl('Ship loadout'), # LANG: Settings > Output option variable=self.out_ship, command=self.outvarchanged ) @@ -348,7 +344,7 @@ class PreferencesDialog(tk.Toplevel): # Output setting self.out_auto_button = nb.Checkbutton( output_frame, - text=_('Automatically update on docking'), # LANG: Settings > Output option + text=tr.tl('Automatically update on docking'), # LANG: Settings > Output option variable=self.out_auto, command=self.outvarchanged ) @@ -357,14 +353,14 @@ class PreferencesDialog(tk.Toplevel): self.outdir = tk.StringVar() self.outdir.set(str(config.get_str('outdir'))) # LANG: Settings > Output - Label for "where files are located" - self.outdir_label = nb.Label(output_frame, text=_('File location')+':') # Section heading in settings + self.outdir_label = nb.Label(output_frame, text=tr.tl('File location')+':') # Section heading in settings # Type ignored due to incorrect type annotation. a 2 tuple does padding for each side self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore self.outdir_entry = ttk.Entry(output_frame, takefocus=False) self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - text = _('Browse...') # LANG: NOT-macOS Settings - files location selection button + text = tr.tl('Browse...') # LANG: NOT-macOS Settings - files location selection button self.outbutton = ttk.Button( output_frame, @@ -372,12 +368,12 @@ class PreferencesDialog(tk.Toplevel): # Technically this is different from the label in Settings > Output, as *this* is used # as the title of the popup folder selection window. # LANG: Settings > Output - Label for "where files are located" - command=lambda: self.filebrowse(_('File location'), self.outdir) + command=lambda: self.filebrowse(tr.tl('File location'), self.outdir) ) self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get()) # LANG: Label for 'Output' Settings/Preferences tab - root_notebook.add(output_frame, text=_('Output')) # Tab heading in settings + root_notebook.add(output_frame, text=tr.tl('Output')) # Tab heading in settings def __setup_plugin_tabs(self, notebook: ttk.Notebook) -> None: for plugin in plug.PLUGINS: @@ -403,19 +399,19 @@ class PreferencesDialog(tk.Toplevel): nb.Label( config_frame, # LANG: Settings > Configuration - Label for Journal files location - text=_('E:D journal file location')+':' + text=tr.tl('E:D journal file location')+':' ).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - text = _('Browse...') # LANG: NOT-macOS Setting - files location selection button + text = tr.tl('Browse...') # LANG: NOT-macOS Setting - files location selection button with row as cur_row: self.logbutton = ttk.Button( config_frame, text=text, # LANG: Settings > Configuration - Label for Journal files location - command=lambda: self.filebrowse(_('E:D journal file location'), self.logdir) + command=lambda: self.filebrowse(tr.tl('E:D journal file location'), self.logdir) ) self.logbutton.grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row) @@ -424,7 +420,7 @@ class PreferencesDialog(tk.Toplevel): ttk.Button( config_frame, # LANG: Settings > Configuration - Label on 'reset journal files location to default' button - text=_('Default'), + text=tr.tl('Default'), command=self.logdir_reset, state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED ).grid(column=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row) @@ -438,13 +434,13 @@ class PreferencesDialog(tk.Toplevel): nb.Label( config_frame, - text=_('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section + text=tr.tl('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) nb.Checkbutton( config_frame, # LANG: Configuration - Enable or disable the Fleet Carrier CAPI calls - text=_('Enable Fleetcarrier CAPI Queries'), + text=tr.tl('Enable Fleetcarrier CAPI Queries'), variable=self.capi_fleetcarrier ).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) @@ -460,7 +456,7 @@ class PreferencesDialog(tk.Toplevel): with row as cur_row: nb.Label( config_frame, - text=_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows + text=tr.tl('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER) @@ -469,7 +465,7 @@ class PreferencesDialog(tk.Toplevel): # No hotkey/shortcut currently defined # TODO: display Only shows up on windows # LANG: No hotkey/shortcut set - hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None') + hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else tr.tl('None') ) self.hotkey_text.bind('', self.hotkeystart) @@ -480,7 +476,7 @@ class PreferencesDialog(tk.Toplevel): self.hotkey_only_btn = nb.Checkbutton( config_frame, # LANG: Configuration - Act on hotkey only when ED is in foreground - text=_('Only when Elite: Dangerous is the active app'), + text=tr.tl('Only when Elite: Dangerous is the active app'), variable=self.hotkey_only, state=tk.NORMAL if self.hotkey_code else tk.DISABLED ) @@ -491,7 +487,7 @@ class PreferencesDialog(tk.Toplevel): self.hotkey_play_btn = nb.Checkbutton( config_frame, # LANG: Configuration - play sound when hotkey used - text=_('Play sound'), + text=tr.tl('Play sound'), variable=self.hotkey_play, state=tk.NORMAL if self.hotkey_code else tk.DISABLED ) @@ -506,7 +502,7 @@ class PreferencesDialog(tk.Toplevel): self.disable_autoappupdatecheckingame_btn = nb.Checkbutton( config_frame, # LANG: Configuration - disable checks for app updates when in-game - text=_('Disable Automatic Application Updates Check when in-game'), + text=tr.tl('Disable Automatic Application Updates Check when in-game'), variable=self.disable_autoappupdatecheckingame, command=self.disable_autoappupdatecheckingame_changed ) @@ -521,7 +517,7 @@ class PreferencesDialog(tk.Toplevel): # Settings prompt for preferred ship loadout, system and station info websites # LANG: Label for preferred shipyard, system and station 'providers' - nb.Label(config_frame, text=_('Preferred websites')).grid( + nb.Label(config_frame, text=tr.tl('Preferred websites')).grid( columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -532,7 +528,9 @@ class PreferencesDialog(tk.Toplevel): ) # Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis # LANG: Label for Shipyard provider selection - nb.Label(config_frame, text=_('Shipyard')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + nb.Label(config_frame, text=tr.tl('Shipyard')).grid( + padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row + ) self.shipyard_button = nb.OptionMenu( config_frame, self.shipyard_provider, self.shipyard_provider.get(), *plug.provides('shipyard_url') ) @@ -544,7 +542,7 @@ class PreferencesDialog(tk.Toplevel): self.alt_shipyard_open_btn = nb.Checkbutton( config_frame, # LANG: Label for checkbox to utilise alternative Coriolis URL method - text=_('Use alternate URL method'), + text=tr.tl('Use alternate URL method'), variable=self.alt_shipyard_open, command=self.alt_shipyard_open_changed, ) @@ -558,7 +556,7 @@ class PreferencesDialog(tk.Toplevel): ) # LANG: Configuration - Label for selection of 'System' provider website - nb.Label(config_frame, text=_('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + nb.Label(config_frame, text=tr.tl('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) self.system_button = nb.OptionMenu( config_frame, self.system_provider, @@ -576,7 +574,7 @@ class PreferencesDialog(tk.Toplevel): ) # LANG: Configuration - Label for selection of 'Station' provider website - nb.Label(config_frame, text=_('Station')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) + nb.Label(config_frame, text=tr.tl('Station')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) self.station_button = nb.OptionMenu( config_frame, self.station_provider, @@ -597,7 +595,7 @@ class PreferencesDialog(tk.Toplevel): nb.Label( config_frame, # LANG: Configuration - Label for selection of Log Level - text=_('Log Level') + text=tr.tl('Log Level') ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row) current_loglevel = config.get_str('loglevel') @@ -624,7 +622,7 @@ class PreferencesDialog(tk.Toplevel): ttk.Button( config_frame, # LANG: Label on button used to open a filesystem folder - text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder + text=tr.tl('Open Log Folder'), # Button that opens a folder in Explorer/Finder command=lambda: help_open_log_folder() ).grid(column=2, padx=self.PADX, pady=0, sticky=tk.NSEW, row=cur_row) @@ -632,7 +630,7 @@ class PreferencesDialog(tk.Toplevel): nb.Label(config_frame).grid(sticky=tk.W, row=row.get()) # LANG: Label for 'Configuration' tab in Settings - notebook.add(config_frame, text=_('Configuration')) + notebook.add(config_frame, text=tr.tl('Configuration')) def __setup_privacy_tab(self, notebook: ttk.Notebook) -> None: privacy_frame = nb.Frame(notebook) @@ -641,37 +639,37 @@ class PreferencesDialog(tk.Toplevel): row = AutoInc(start=0) # LANG: UI elements privacy section header in privacy tab of preferences - nb.Label(privacy_frame, text=_('Main UI privacy options')).grid( + nb.Label(privacy_frame, text=tr.tl('Main UI privacy options')).grid( row=row.get(), column=0, sticky=tk.W, padx=self.PADX, pady=self.PADY ) nb.Checkbutton( # LANG: Hide private group owner name from UI checkbox - privacy_frame, text=_('Hide private group name in UI'), + privacy_frame, text=tr.tl('Hide private group name in UI'), variable=self.hide_private_group ).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W) nb.Checkbutton( # LANG: Hide multicrew captain name from main UI checkbox - privacy_frame, text=_('Hide multi-crew captain name'), + privacy_frame, text=tr.tl('Hide multi-crew captain name'), variable=self.hide_multicrew_captain ).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W) - notebook.add(privacy_frame, text=_('Privacy')) # LANG: Preferences privacy tab title + notebook.add(privacy_frame, text=tr.tl('Privacy')) # LANG: Preferences privacy tab title def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None: - self.languages = Translations.available_names() + self.languages = tr.available_names() # Appearance theme and language setting # LANG: The system default language choice in Settings > Appearance - self.lang = tk.StringVar(value=self.languages.get(config.get_str('language'), _('Default'))) + self.lang = tk.StringVar(value=self.languages.get(config.get_str('language'), tr.tl('Default'))) self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) self.minimize_system_tray = tk.BooleanVar(value=config.get_bool('minimize_system_tray')) self.theme = tk.IntVar(value=config.get_int('theme')) self.theme_colors = [config.get_str('dark_text'), config.get_str('dark_highlight')] self.theme_prompts = [ # LANG: Label for Settings > Appeareance > selection of 'normal' text colour - _('Normal text'), # Dark theme color setting + tr.tl('Normal text'), # Dark theme color setting # LANG: Label for Settings > Appeareance > selection of 'highlightes' text colour - _('Highlighted text'), # Dark theme color setting + tr.tl('Highlighted text'), # Dark theme color setting ] row = AutoInc(start=0) @@ -680,7 +678,7 @@ class PreferencesDialog(tk.Toplevel): appearance_frame.columnconfigure(2, weight=1) with row as cur_row: # LANG: Appearance - Label for selection of application display language - nb.Label(appearance_frame, text=_('Language')).grid( + nb.Label(appearance_frame, text=tr.tl('Language')).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) self.lang_button = nb.OptionMenu(appearance_frame, self.lang, self.lang.get(), *self.languages.values()) @@ -692,28 +690,29 @@ class PreferencesDialog(tk.Toplevel): # Appearance setting # LANG: Label for Settings > Appearance > Theme selection - nb.Label(appearance_frame, text=_('Theme')).grid( + nb.Label(appearance_frame, text=tr.tl('Theme')).grid( columnspan=3, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) # Appearance theme and language setting nb.Radiobutton( # LANG: Label for 'Default' theme radio button - appearance_frame, text=_('Default'), variable=self.theme, + appearance_frame, text=tr.tl('Default'), variable=self.theme, value=theme.THEME_DEFAULT, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance theme setting nb.Radiobutton( # LANG: Label for 'Dark' theme radio button - appearance_frame, text=_('Dark'), variable=self.theme, value=theme.THEME_DARK, command=self.themevarchanged + appearance_frame, text=tr.tl('Dark'), variable=self.theme, + value=theme.THEME_DARK, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) if sys.platform == 'win32': nb.Radiobutton( appearance_frame, # LANG: Label for 'Transparent' theme radio button - text=_('Transparent'), # Appearance theme setting + text=tr.tl('Transparent'), # Appearance theme setting variable=self.theme, value=theme.THEME_TRANSPARENT, command=self.themevarchanged @@ -727,7 +726,7 @@ class PreferencesDialog(tk.Toplevel): self.theme_button_0 = tk.Button( appearance_frame, # LANG: Appearance - Example 'Normal' text - text=_('Station'), + text=tr.tl('Station'), background='grey4', command=lambda: self.themecolorbrowse(0) ) @@ -759,7 +758,7 @@ class PreferencesDialog(tk.Toplevel): ) with row as cur_row: # LANG: Appearance - Label for selection of UI scaling - nb.Label(appearance_frame, text=_('UI Scale Percentage')).grid( + nb.Label(appearance_frame, text=tr.tl('UI Scale Percentage')).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) @@ -780,7 +779,7 @@ class PreferencesDialog(tk.Toplevel): self.ui_scaling_defaultis = nb.Label( appearance_frame, # LANG: Appearance - Help/hint text for UI scaling selection - text=_('100 means Default{CR}Restart Required for{CR}changes to take effect!') + text=tr.tl('100 means Default{CR}Restart Required for{CR}changes to take effect!') ).grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.E, row=cur_row) # Transparency slider @@ -790,7 +789,7 @@ class PreferencesDialog(tk.Toplevel): with row as cur_row: # LANG: Appearance - Label for selection of main window transparency - nb.Label(appearance_frame, text=_("Main window transparency")).grid( + nb.Label(appearance_frame, text=tr.tl("Main window transparency")).grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row ) self.transparency = tk.IntVar() @@ -810,7 +809,7 @@ class PreferencesDialog(tk.Toplevel): nb.Label( appearance_frame, # LANG: Appearance - Help/hint text for Main window transparency selection - text=_( + text=tr.tl( "100 means fully opaque.{CR}" "Window is updated in real time" ).format(CR='\n') @@ -832,7 +831,7 @@ class PreferencesDialog(tk.Toplevel): self.ontop_button = nb.Checkbutton( appearance_frame, # LANG: Appearance - Label for checkbox to select if application always on top - text=_('Always on top'), + text=tr.tl('Always on top'), variable=self.always_ontop, command=self.themevarchanged ) @@ -844,7 +843,7 @@ class PreferencesDialog(tk.Toplevel): nb.Checkbutton( appearance_frame, # LANG: Appearance option for Windows "minimize to system tray" - text=_('Minimize to system tray'), + text=tr.tl('Minimize to system tray'), variable=self.minimize_system_tray, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance setting @@ -852,7 +851,7 @@ class PreferencesDialog(tk.Toplevel): nb.Label(appearance_frame).grid(sticky=tk.W) # big spacer # LANG: Label for Settings > Appearance tab - notebook.add(appearance_frame, text=_('Appearance')) # Tab heading in settings + notebook.add(appearance_frame, text=tr.tl('Appearance')) # Tab heading in settings def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001 # Plugin settings and info @@ -864,7 +863,7 @@ class PreferencesDialog(tk.Toplevel): # Section heading in settings # LANG: Label for location of third-party plugins folder - nb.Label(plugins_frame, text=_('Plugins folder') + ':').grid( + nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -877,13 +876,14 @@ class PreferencesDialog(tk.Toplevel): plugins_frame, # Help text in settings # LANG: Tip/label about how to disable plugins - text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled') + text=tr.tl( + "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled') ).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row) ttk.Button( plugins_frame, # LANG: Label on button used to open a filesystem folder - text=_('Open'), # Button that opens a folder in Explorer/Finder + text=tr.tl('Open'), # Button that opens a folder in Explorer/Finder command=lambda: webbrowser.open(f'file:///{config.plugin_dir_path}') ).grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.N, row=cur_row) @@ -895,7 +895,7 @@ class PreferencesDialog(tk.Toplevel): nb.Label( plugins_frame, # LANG: Label on list of enabled plugins - text=_('Enabled Plugins')+':' # List of plugins in settings + text=tr.tl('Enabled Plugins')+':' # List of plugins in settings ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) for plugin in enabled_plugins: @@ -915,13 +915,13 @@ class PreferencesDialog(tk.Toplevel): columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) # LANG: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x - nb.Label(plugins_frame, text=_('Plugins Without Python 3.x Support')+':').grid( + nb.Label(plugins_frame, text=tr.tl('Plugins Without Python 3.x Support')+':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) HyperlinkLabel( # LANG: Plugins - Label on URL to documentation about migrating plugins from Python 2.7 - plugins_frame, text=_('Information on migrating plugins'), + plugins_frame, text=tr.tl('Information on migrating plugins'), background=nb.Label().cget('background'), url='https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#migration-from-python-27', underline=True @@ -943,7 +943,7 @@ class PreferencesDialog(tk.Toplevel): nb.Label( plugins_frame, # LANG: Label on list of user-disabled plugins - text=_('Disabled Plugins')+':' # List of plugins in settings + text=tr.tl('Disabled Plugins')+':' # List of plugins in settings ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) for plugin in disabled_plugins: @@ -958,7 +958,7 @@ class PreferencesDialog(tk.Toplevel): columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get() ) # LANG: Plugins - Label for list of 'broken' plugins that failed to load - nb.Label(plugins_frame, text=_('Broken Plugins')+':').grid( + nb.Label(plugins_frame, text=tr.tl('Broken Plugins')+':').grid( padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() ) @@ -969,7 +969,7 @@ class PreferencesDialog(tk.Toplevel): ) # LANG: Label on Settings > Plugins tab - notebook.add(plugins_frame, text=_('Plugins')) # Tab heading in settings + notebook.add(plugins_frame, text=tr.tl('Plugins')) # Tab heading in settings def cmdrchanged(self, event=None): """ @@ -1122,7 +1122,7 @@ class PreferencesDialog(tk.Toplevel): self.hotkey_text.insert( 0, # LANG: No hotkey/shortcut set - hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')) + hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else tr.tl('None')) def hotkeylisten(self, event: 'tk.Event[Any]') -> str: """ @@ -1155,7 +1155,7 @@ class PreferencesDialog(tk.Toplevel): else: # LANG: No hotkey/shortcut set - event.widget.insert(0, _('None')) + event.widget.insert(0, tr.tl('None')) self.hotkey_only_btn['state'] = tk.DISABLED self.hotkey_play_btn['state'] = tk.DISABLED @@ -1205,7 +1205,7 @@ class PreferencesDialog(tk.Toplevel): lang_codes = {v: k for k, v in self.languages.items()} # Codes by name config.set('language', lang_codes.get(self.lang.get()) or '') # or '' used here due to Default being None above - Translations.install(config.get_str('language', default=None)) # type: ignore # This sets self in weird ways. + tr.install(config.get_str('language', default=None)) # type: ignore # This sets self in weird ways. # Privacy options config.set('hide_private_group', self.hide_private_group.get()) diff --git a/stats.py b/stats.py index c377e5d3..db61f789 100644 --- a/stats.py +++ b/stats.py @@ -12,20 +12,17 @@ import json import sys import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Any, AnyStr, Callable, NamedTuple, Sequence, cast +from typing import Any, AnyStr, Callable, NamedTuple, Sequence, cast import companion import EDMCLogging import myNotebook as nb # noqa: N813 from edmc_data import ship_name_map from hotkey import hotkeymgr -from l10n import Locale +from l10n import Locale, translations as tr from monitor import monitor logger = EDMCLogging.get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: return x - if sys.platform == 'win32': import ctypes from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT @@ -60,32 +57,32 @@ def status(data: dict[str, Any]) -> list[list[str]]: """ # StatsResults assumes these three things are first res = [ - [_('Cmdr'), data['commander']['name']], # LANG: Cmdr stats - [_('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats - [_('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats + [tr.tl('Cmdr'), data['commander']['name']], # LANG: Cmdr stats + [tr.tl('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats + [tr.tl('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats ] _ELITE_RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime - _('Elite'), # LANG: Top rank - _('Elite I'), # LANG: Top rank +1 - _('Elite II'), # LANG: Top rank +2 - _('Elite III'), # LANG: Top rank +3 - _('Elite IV'), # LANG: Top rank +4 - _('Elite V'), # LANG: Top rank +5 + tr.tl('Elite'), # LANG: Top rank + tr.tl('Elite I'), # LANG: Top rank +1 + tr.tl('Elite II'), # LANG: Top rank +2 + tr.tl('Elite III'), # LANG: Top rank +3 + tr.tl('Elite IV'), # LANG: Top rank +4 + tr.tl('Elite V'), # LANG: Top rank +5 ] RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime # in output order # Names we show people, vs internal names - (_('Combat'), 'combat'), # LANG: Ranking - (_('Trade'), 'trade'), # LANG: Ranking - (_('Explorer'), 'explore'), # LANG: Ranking - (_('Mercenary'), 'soldier'), # LANG: Ranking - (_('Exobiologist'), 'exobiologist'), # LANG: Ranking - (_('CQC'), 'cqc'), # LANG: Ranking - (_('Federation'), 'federation'), # LANG: Ranking - (_('Empire'), 'empire'), # LANG: Ranking - (_('Powerplay'), 'power'), # LANG: Ranking + (tr.tl('Combat'), 'combat'), # LANG: Ranking + (tr.tl('Trade'), 'trade'), # LANG: Ranking + (tr.tl('Explorer'), 'explore'), # LANG: Ranking + (tr.tl('Mercenary'), 'soldier'), # LANG: Ranking + (tr.tl('Exobiologist'), 'exobiologist'), # LANG: Ranking + (tr.tl('CQC'), 'cqc'), # LANG: Ranking + (tr.tl('Federation'), 'federation'), # LANG: Ranking + (tr.tl('Empire'), 'empire'), # LANG: Ranking + (tr.tl('Powerplay'), 'power'), # LANG: Ranking # ??? , 'crime'), # LANG: Ranking # ??? , 'service'), # LANG: Ranking ] @@ -94,113 +91,113 @@ def status(data: dict[str, Any]) -> list[list[str]]: # These names are the fdev side name (but lower()ed) # http://elite-dangerous.wikia.com/wiki/Pilots_Federation#Ranks 'combat': [ - _('Harmless'), # LANG: Combat rank - _('Mostly Harmless'), # LANG: Combat rank - _('Novice'), # LANG: Combat rank - _('Competent'), # LANG: Combat rank - _('Expert'), # LANG: Combat rank - _('Master'), # LANG: Combat rank - _('Dangerous'), # LANG: Combat rank - _('Deadly'), # LANG: Combat rank + tr.tl('Harmless'), # LANG: Combat rank + tr.tl('Mostly Harmless'), # LANG: Combat rank + tr.tl('Novice'), # LANG: Combat rank + tr.tl('Competent'), # LANG: Combat rank + tr.tl('Expert'), # LANG: Combat rank + tr.tl('Master'), # LANG: Combat rank + tr.tl('Dangerous'), # LANG: Combat rank + tr.tl('Deadly'), # LANG: Combat rank ] + _ELITE_RANKS, 'trade': [ - _('Penniless'), # LANG: Trade rank - _('Mostly Penniless'), # LANG: Trade rank - _('Peddler'), # LANG: Trade rank - _('Dealer'), # LANG: Trade rank - _('Merchant'), # LANG: Trade rank - _('Broker'), # LANG: Trade rank - _('Entrepreneur'), # LANG: Trade rank - _('Tycoon'), # LANG: Trade rank + tr.tl('Penniless'), # LANG: Trade rank + tr.tl('Mostly Penniless'), # LANG: Trade rank + tr.tl('Peddler'), # LANG: Trade rank + tr.tl('Dealer'), # LANG: Trade rank + tr.tl('Merchant'), # LANG: Trade rank + tr.tl('Broker'), # LANG: Trade rank + tr.tl('Entrepreneur'), # LANG: Trade rank + tr.tl('Tycoon'), # LANG: Trade rank ] + _ELITE_RANKS, 'explore': [ - _('Aimless'), # LANG: Explorer rank - _('Mostly Aimless'), # LANG: Explorer rank - _('Scout'), # LANG: Explorer rank - _('Surveyor'), # LANG: Explorer rank - _('Trailblazer'), # LANG: Explorer rank - _('Pathfinder'), # LANG: Explorer rank - _('Ranger'), # LANG: Explorer rank - _('Pioneer'), # LANG: Explorer rank + tr.tl('Aimless'), # LANG: Explorer rank + tr.tl('Mostly Aimless'), # LANG: Explorer rank + tr.tl('Scout'), # LANG: Explorer rank + tr.tl('Surveyor'), # LANG: Explorer rank + tr.tl('Trailblazer'), # LANG: Explorer rank + tr.tl('Pathfinder'), # LANG: Explorer rank + tr.tl('Ranger'), # LANG: Explorer rank + tr.tl('Pioneer'), # LANG: Explorer rank ] + _ELITE_RANKS, 'soldier': [ - _('Defenceless'), # LANG: Mercenary rank - _('Mostly Defenceless'), # LANG: Mercenary rank - _('Rookie'), # LANG: Mercenary rank - _('Soldier'), # LANG: Mercenary rank - _('Gunslinger'), # LANG: Mercenary rank - _('Warrior'), # LANG: Mercenary rank - _('Gunslinger'), # LANG: Mercenary rank - _('Deadeye'), # LANG: Mercenary rank + tr.tl('Defenceless'), # LANG: Mercenary rank + tr.tl('Mostly Defenceless'), # LANG: Mercenary rank + tr.tl('Rookie'), # LANG: Mercenary rank + tr.tl('Soldier'), # LANG: Mercenary rank + tr.tl('Gunslinger'), # LANG: Mercenary rank + tr.tl('Warrior'), # LANG: Mercenary rank + tr.tl('Gunslinger'), # LANG: Mercenary rank + tr.tl('Deadeye'), # LANG: Mercenary rank ] + _ELITE_RANKS, 'exobiologist': [ - _('Directionless'), # LANG: Exobiologist rank - _('Mostly Directionless'), # LANG: Exobiologist rank - _('Compiler'), # LANG: Exobiologist rank - _('Collector'), # LANG: Exobiologist rank - _('Cataloguer'), # LANG: Exobiologist rank - _('Taxonomist'), # LANG: Exobiologist rank - _('Ecologist'), # LANG: Exobiologist rank - _('Geneticist'), # LANG: Exobiologist rank + tr.tl('Directionless'), # LANG: Exobiologist rank + tr.tl('Mostly Directionless'), # LANG: Exobiologist rank + tr.tl('Compiler'), # LANG: Exobiologist rank + tr.tl('Collector'), # LANG: Exobiologist rank + tr.tl('Cataloguer'), # LANG: Exobiologist rank + tr.tl('Taxonomist'), # LANG: Exobiologist rank + tr.tl('Ecologist'), # LANG: Exobiologist rank + tr.tl('Geneticist'), # LANG: Exobiologist rank ] + _ELITE_RANKS, 'cqc': [ - _('Helpless'), # LANG: CQC rank - _('Mostly Helpless'), # LANG: CQC rank - _('Amateur'), # LANG: CQC rank - _('Semi Professional'), # LANG: CQC rank - _('Professional'), # LANG: CQC rank - _('Champion'), # LANG: CQC rank - _('Hero'), # LANG: CQC rank - _('Gladiator'), # LANG: CQC rank + tr.tl('Helpless'), # LANG: CQC rank + tr.tl('Mostly Helpless'), # LANG: CQC rank + tr.tl('Amateur'), # LANG: CQC rank + tr.tl('Semi Professional'), # LANG: CQC rank + tr.tl('Professional'), # LANG: CQC rank + tr.tl('Champion'), # LANG: CQC rank + tr.tl('Hero'), # LANG: CQC rank + tr.tl('Gladiator'), # LANG: CQC rank ] + _ELITE_RANKS, # http://elite-dangerous.wikia.com/wiki/Federation#Ranks 'federation': [ - _('None'), # LANG: No rank - _('Recruit'), # LANG: Federation rank - _('Cadet'), # LANG: Federation rank - _('Midshipman'), # LANG: Federation rank - _('Petty Officer'), # LANG: Federation rank - _('Chief Petty Officer'), # LANG: Federation rank - _('Warrant Officer'), # LANG: Federation rank - _('Ensign'), # LANG: Federation rank - _('Lieutenant'), # LANG: Federation rank - _('Lieutenant Commander'), # LANG: Federation rank - _('Post Commander'), # LANG: Federation rank - _('Post Captain'), # LANG: Federation rank - _('Rear Admiral'), # LANG: Federation rank - _('Vice Admiral'), # LANG: Federation rank - _('Admiral') # LANG: Federation rank + tr.tl('None'), # LANG: No rank + tr.tl('Recruit'), # LANG: Federation rank + tr.tl('Cadet'), # LANG: Federation rank + tr.tl('Midshipman'), # LANG: Federation rank + tr.tl('Petty Officer'), # LANG: Federation rank + tr.tl('Chief Petty Officer'), # LANG: Federation rank + tr.tl('Warrant Officer'), # LANG: Federation rank + tr.tl('Ensign'), # LANG: Federation rank + tr.tl('Lieutenant'), # LANG: Federation rank + tr.tl('Lieutenant Commander'), # LANG: Federation rank + tr.tl('Post Commander'), # LANG: Federation rank + tr.tl('Post Captain'), # LANG: Federation rank + tr.tl('Rear Admiral'), # LANG: Federation rank + tr.tl('Vice Admiral'), # LANG: Federation rank + tr.tl('Admiral') # LANG: Federation rank ], # http://elite-dangerous.wikia.com/wiki/Empire#Ranks 'empire': [ - _('None'), # LANG: No rank - _('Outsider'), # LANG: Empire rank - _('Serf'), # LANG: Empire rank - _('Master'), # LANG: Empire rank - _('Squire'), # LANG: Empire rank - _('Knight'), # LANG: Empire rank - _('Lord'), # LANG: Empire rank - _('Baron'), # LANG: Empire rank - _('Viscount'), # LANG: Empire rank - _('Count'), # LANG: Empire rank - _('Earl'), # LANG: Empire rank - _('Marquis'), # LANG: Empire rank - _('Duke'), # LANG: Empire rank - _('Prince'), # LANG: Empire rank - _('King') # LANG: Empire rank + tr.tl('None'), # LANG: No rank + tr.tl('Outsider'), # LANG: Empire rank + tr.tl('Serf'), # LANG: Empire rank + tr.tl('Master'), # LANG: Empire rank + tr.tl('Squire'), # LANG: Empire rank + tr.tl('Knight'), # LANG: Empire rank + tr.tl('Lord'), # LANG: Empire rank + tr.tl('Baron'), # LANG: Empire rank + tr.tl('Viscount'), # LANG: Empire rank + tr.tl('Count'), # LANG: Empire rank + tr.tl('Earl'), # LANG: Empire rank + tr.tl('Marquis'), # LANG: Empire rank + tr.tl('Duke'), # LANG: Empire rank + tr.tl('Prince'), # LANG: Empire rank + tr.tl('King') # LANG: Empire rank ], # http://elite-dangerous.wikia.com/wiki/Ratings 'power': [ - _('None'), # LANG: No rank - _('Rating 1'), # LANG: Power rank - _('Rating 2'), # LANG: Power rank - _('Rating 3'), # LANG: Power rank - _('Rating 4'), # LANG: Power rank - _('Rating 5') # LANG: Power rank + tr.tl('None'), # LANG: No rank + tr.tl('Rating 1'), # LANG: Power rank + tr.tl('Rating 2'), # LANG: Power rank + tr.tl('Rating 3'), # LANG: Power rank + tr.tl('Rating 4'), # LANG: Power rank + tr.tl('Rating 5') # LANG: Power rank ], } @@ -212,7 +209,7 @@ def status(data: dict[str, Any]) -> list[list[str]]: res.append([title, names[rank] if rank < len(names) else f'Rank {rank}']) else: - res.append([title, _('None')]) # LANG: No rank + res.append([title, tr.tl('None')]) # LANG: No rank return res @@ -318,7 +315,7 @@ class StatsDialog(): if not monitor.cmdr: hotkeymgr.play_bad() # LANG: Current commander unknown when trying to use 'File' > 'Status' - self.status['text'] = _("Status: Don't yet know your Commander name") + self.status['text'] = tr.tl("Status: Don't yet know your Commander name") return # TODO: This needs to use cached data @@ -326,7 +323,7 @@ class StatsDialog(): logger.info('No cached data, aborting...') hotkeymgr.play_bad() # LANG: No Frontier CAPI data yet when trying to use 'File' > 'Status' - self.status['text'] = _("Status: No CAPI data yet") + self.status['text'] = tr.tl("Status: No CAPI data yet") return capi_data = json.loads( @@ -336,7 +333,7 @@ class StatsDialog(): if not capi_data.get('commander') or not capi_data['commander'].get('name', '').strip(): # Shouldn't happen # LANG: Unknown commander - self.status['text'] = _("Who are you?!") + self.status['text'] = tr.tl("Who are you?!") elif ( not capi_data.get('lastSystem') @@ -344,7 +341,7 @@ class StatsDialog(): ): # Shouldn't happen # LANG: Unknown location - self.status['text'] = _("Where are you?!") + self.status['text'] = tr.tl("Where are you?!") elif ( not capi_data.get('ship') or not capi_data['ship'].get('modules') @@ -352,7 +349,7 @@ class StatsDialog(): ): # Shouldn't happen # LANG: Unknown ship - self.status['text'] = _("What are you flying?!") + self.status['text'] = tr.tl("What are you flying?!") else: self.status['text'] = '' @@ -401,14 +398,14 @@ class StatsResults(tk.Toplevel): self.addpagerow(page, thing, with_copy=True) ttk.Frame(page).grid(pady=5) # bottom spacer - notebook.add(page, text=_('Status')) # LANG: Status dialog title + notebook.add(page, text=tr.tl('Status')) # LANG: Status dialog title page = self.addpage(notebook, [ - _('Ship'), # LANG: Status dialog subtitle + tr.tl('Ship'), # LANG: Status dialog subtitle '', - _('System'), # LANG: Main window - _('Station'), # LANG: Status dialog subtitle - _('Value'), # LANG: Status dialog subtitle - CR value of ship + tr.tl('System'), # LANG: Main window + tr.tl('Station'), # LANG: Status dialog subtitle + tr.tl('Value'), # LANG: Status dialog subtitle - CR value of ship ]) shiplist = ships(data) @@ -417,7 +414,7 @@ class StatsResults(tk.Toplevel): self.addpagerow(page, list(ship_data[1:-1]) + [self.credits(int(ship_data[-1]))], with_copy=True) ttk.Frame(page).grid(pady=5) # bottom spacer - notebook.add(page, text=_('Ships')) # LANG: Status dialog title + notebook.add(page, text=tr.tl('Ships')) # LANG: Status dialog title # wait for window to appear on screen before calling grab_set self.wait_visibility() diff --git a/theme.py b/theme.py index bbe62ef5..94e99f7a 100644 --- a/theme.py +++ b/theme.py @@ -16,17 +16,14 @@ import tkinter as tk from os.path import join from tkinter import font as tk_font from tkinter import ttk -from typing import TYPE_CHECKING, Callable - +from typing import Callable +from l10n import translations as tr from config import config from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel logger = get_main_logger() -if TYPE_CHECKING: - def _(x: str) -> str: ... - if __debug__: from traceback import print_exc @@ -291,7 +288,7 @@ class _Theme: # Font only supports Latin 1 / Supplement / Extended, and a # few General Punctuation and Mathematical Operators # LANG: Label for commander name in main window - 'font': (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and + 'font': (theme > 1 and not 0x250 < ord(tr.tl('Cmdr')[0]) < 0x3000 and tk_font.Font(family='Euro Caps', size=10, weight=tk_font.NORMAL) or 'TkDefaultFont'), } diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 9bb3b9bf..d026f619 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -25,10 +25,8 @@ import tkinter as tk import webbrowser from tkinter import font as tk_font from tkinter import ttk -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - def _(x: str) -> str: return x +from typing import Any +from l10n import translations as tr class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore @@ -55,7 +53,7 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore self.menu = tk.Menu(tearoff=tk.FALSE) # LANG: Label for 'Copy' as in 'Copy and Paste' - self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste + self.menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste self.bind('', self._contextmenu) self.bind('', self._enter) diff --git a/update.py b/update.py index 991558d6..52983a5d 100644 --- a/update.py +++ b/update.py @@ -16,10 +16,10 @@ import requests import semantic_version from config import appname, appversion_nobuild, config, update_feed from EDMCLogging import get_main_logger +from l10n import translations as tr if TYPE_CHECKING: import tkinter as tk - def _(x: str): return x logger = get_main_logger() @@ -200,7 +200,7 @@ class Updater: if newversion and self.root: status = self.root.nametowidget(f'.{appname.lower()}.status') # LANG: Update Available Text - status['text'] = _("{NEWVER} is available").format(NEWVER=newversion.title) + status['text'] = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title) self.root.update_idletasks() else: