1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 15:27:14 +03:00

[1812] Handover Translations

This commit is contained in:
David Sangrey 2024-04-22 17:33:28 -04:00
parent 673251542e
commit 080d9f98f2
No known key found for this signature in database
GPG Key ID: 3AEADBB0186884BC
15 changed files with 415 additions and 423 deletions

View File

@ -26,7 +26,6 @@ from EDMCLogging import edmclogger, logger, logging
if TYPE_CHECKING: if TYPE_CHECKING:
from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy
def _(x: str): return x
edmclogger.set_channels_loglevel(logging.INFO) edmclogger.set_channels_loglevel(logging.INFO)
@ -35,7 +34,7 @@ import collate
import commodity import commodity
import companion import companion
import edshipyard import edshipyard
import l10n from l10n import translations as tr
import loadout import loadout
import outfitting import outfitting
import shipyard 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] SERVER_RETRY = 5 # retry pause for Companion servers [s]
EXIT_SUCCESS, EXIT_SERVER, EXIT_CREDENTIALS, EXIT_VERIFICATION, EXIT_LAGGING, EXIT_SYS_ERR, EXIT_ARGS, \ 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() newversion: EDMCVersion | None = updater.check_appcast()
if newversion: if newversion:
# LANG: Update Available Text # 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})') print(f'{appversion()} ({newverstr})')
else: else:
print(appversion()) print(appversion())

View File

@ -413,9 +413,6 @@ if TYPE_CHECKING:
from infi.systray import SysTrayIcon from infi.systray import SysTrayIcon
# isort: on # isort: on
def _(x: str) -> str:
"""Fake the l10n translation functions for typing."""
return x
import tkinter as tk import tkinter as tk
import tkinter.filedialog import tkinter.filedialog
@ -432,7 +429,7 @@ from commodity import COMMODITY_CSV
from dashboard import dashboard from dashboard import dashboard
from edmc_data import ship_name_map from edmc_data import ship_name_map
from hotkey import hotkeymgr from hotkey import hotkeymgr
from l10n import Translations from l10n import translations as tr
from monitor import monitor from monitor import monitor
from theme import theme from theme import theme
from ttkHyperlinkLabel import HyperlinkLabel from ttkHyperlinkLabel import HyperlinkLabel
@ -590,7 +587,7 @@ class AppWindow:
self.button = ttk.Button( self.button = ttk.Button(
frame, frame,
name='update_button', name='update_button',
text=_('Update'), # LANG: Main UI Update button text=tr.tl('Update'), # LANG: Main UI Update button
width=28, width=28,
default=tk.ACTIVE, default=tk.ACTIVE,
state=tk.DISABLED state=tk.DISABLED
@ -660,7 +657,7 @@ class AppWindow:
self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
self.system_menu.add_separator() self.system_menu.add_separator()
# LANG: Appearance - Label for checkbox to select if application always on top # 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, variable=self.always_ontop,
command=self.ontop_changed) # Appearance setting command=self.ontop_changed) # Appearance setting
self.menubar.add_cascade(menu=self.system_menu) self.menubar.add_cascade(menu=self.system_menu)
@ -765,7 +762,7 @@ class AppWindow:
# Check for Valid Providers # Check for Valid Providers
validate_providers() validate_providers()
if monitor.cmdr is None: 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. # Start a protocol handler to handle cAPI registration. Requires main loop to be running.
self.w.after_idle(lambda: protocol.protocolhandler.start(self.w)) self.w.after_idle(lambda: protocol.protocolhandler.start(self.w))
@ -795,7 +792,7 @@ class AppWindow:
suit = monitor.state.get('SuitCurrent') suit = monitor.state.get('SuitCurrent')
if suit is None: if suit is None:
self.suit['text'] = f'<{_("Unknown")}>' # LANG: Unknown suit self.suit['text'] = f'<{tr.tl("Unknown")}>' # LANG: Unknown suit
return return
suitname = suit['edmcName'] suitname = suit['edmcName']
@ -851,45 +848,45 @@ class AppWindow:
# (Re-)install log monitoring # (Re-)install log monitoring
if not monitor.start(self.w): if not monitor.start(self.w):
# LANG: ED Journal file location appears to be in error # 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: if dologin and monitor.cmdr:
self.login() # Login if not already logged in with this Cmdr self.login() # Login if not already logged in with this Cmdr
def set_labels(self): def set_labels(self):
"""Set main window labels, e.g. after language change.""" """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 # 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.ship_label['text'] = (monitor.state['Captain'] and tr.tl('Role') or tr.tl('Ship')) + ':' # Main window
self.suit_label['text'] = _('Suit') + ':' # LANG: Label for 'Suit' line in main UI self.suit_label['text'] = tr.tl('Suit') + ':' # LANG: Label for 'Suit' line in main UI
self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI self.system_label['text'] = tr.tl('System') + ':' # LANG: Label for 'System' line in main UI
self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' 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'] = _('Update') # LANG: Update button in main window self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title self.menubar.entryconfigure(1, label=tr.tl('File')) # LANG: 'File' menu title
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title self.menubar.entryconfigure(2, label=tr.tl('Edit')) # LANG: 'Edit' menu title
self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title self.menubar.entryconfigure(3, label=tr.tl('Help')) # LANG: 'Help' menu title
self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title self.theme_file_menu['text'] = tr.tl('File') # LANG: 'File' menu title
self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title self.theme_edit_menu['text'] = tr.tl('Edit') # LANG: 'Edit' menu title
self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title self.theme_help_menu['text'] = tr.tl('Help') # LANG: 'Help' menu title
# File menu # File menu
self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status self.file_menu.entryconfigure(0, label=tr.tl('Status')) # LANG: File > Status
self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data... self.file_menu.entryconfigure(1, label=tr.tl('Save Raw Data...')) # LANG: File > Save Raw Data...
self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings self.file_menu.entryconfigure(2, label=tr.tl('Settings')) # LANG: File > Settings
self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit self.file_menu.entryconfigure(4, label=tr.tl('Exit')) # LANG: File > Exit
# Help menu # Help menu
self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation self.help_menu.entryconfigure(0, label=tr.tl('Documentation')) # LANG: Help > Documentation
self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting self.help_menu.entryconfigure(1, label=tr.tl('Troubleshooting')) # LANG: Help > Troubleshooting
self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug self.help_menu.entryconfigure(2, label=tr.tl('Report A Bug')) # LANG: Help > Report A Bug
self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy self.help_menu.entryconfigure(3, label=tr.tl('Privacy Policy')) # LANG: Help > Privacy Policy
self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes self.help_menu.entryconfigure(4, label=tr.tl('Release Notes')) # LANG: Help > Release Notes
self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates... self.help_menu.entryconfigure(5, label=tr.tl('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(6, label=tr.tl("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(7, label=tr.tl('Open Log Folder')) # LANG: Help > Open Log Folder
# Edit menu # 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): def login(self):
"""Initiate CAPI/Frontier login and set other necessary state.""" """Initiate CAPI/Frontier login and set other necessary state."""
@ -900,12 +897,12 @@ class AppWindow:
if should_return: if should_return:
logger.warning('capi.auth has been disabled via killswitch. Returning.') logger.warning('capi.auth has been disabled via killswitch. Returning.')
# LANG: CAPI auth aborted because of killswitch # 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 return
if not self.status['text']: if not self.status['text']:
# LANG: Status - Attempting to get a Frontier Auth Access Token # 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 self.button['state'] = self.theme_button['state'] = tk.DISABLED
@ -916,7 +913,7 @@ class AppWindow:
try: try:
if companion.session.login(monitor.cmdr, monitor.is_beta): if companion.session.login(monitor.cmdr, monitor.is_beta):
# LANG: Successfully authenticated with the Frontier website # 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(0, state=tk.NORMAL) # Status
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data 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 # Signal as error because the user might actually be docked
# but the server hosting the Companion API hasn't caught up # 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 # 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 return False
# Ignore possibly missing shipyard info # Ignore possibly missing shipyard info
if output_flags & config.OUT_EDDN_SEND_STATION_DATA and not (has_commodities or has_modules): 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 # 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: elif not has_commodities:
# LANG: Status - No station market data from Frontier CAPI # 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: elif output_flags & commodities_flag:
# Fixup anomalies in the comodity data # Fixup anomalies in the comodity data
@ -995,7 +992,7 @@ class AppWindow:
if should_return: if should_return:
logger.warning('capi.auth has been disabled via killswitch. Returning.') logger.warning('capi.auth has been disabled via killswitch. Returning.')
# LANG: CAPI auth query aborted because of killswitch # 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() hotkeymgr.play_bad()
return return
@ -1005,37 +1002,37 @@ class AppWindow:
if not monitor.cmdr: if not monitor.cmdr:
logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown')
# LANG: CAPI queries aborted because Cmdr name is 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 return
if not monitor.mode: if not monitor.mode:
logger.trace_if('capi.worker', 'Aborting Query: Game Mode unknown') logger.trace_if('capi.worker', 'Aborting Query: Game Mode unknown')
# LANG: CAPI queries aborted because 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 return
if monitor.state['GameVersion'] is None: if monitor.state['GameVersion'] is None:
logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown') logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown')
# LANG: CAPI queries aborted because 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 return
if not monitor.state['SystemName']: if not monitor.state['SystemName']:
logger.trace_if('capi.worker', 'Aborting Query: Current star system unknown') logger.trace_if('capi.worker', 'Aborting Query: Current star system unknown')
# LANG: CAPI queries aborted because current star system name 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 return
if monitor.state['Captain']: if monitor.state['Captain']:
logger.trace_if('capi.worker', 'Aborting Query: In multi-crew') 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 # 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 return
if monitor.mode == 'CQC': if monitor.mode == 'CQC':
logger.trace_if('capi.worker', 'Aborting Query: In CQC') logger.trace_if('capi.worker', 'Aborting Query: In CQC')
# LANG: CAPI queries aborted because player is in CQC (Arena) # 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 return
if companion.session.state == companion.Session.STATE_AUTH: if companion.session.state == companion.Session.STATE_AUTH:
@ -1056,7 +1053,7 @@ class AppWindow:
hotkeymgr.play_good() hotkeymgr.play_good()
# LANG: Status - Attempting to retrieve data from Frontier CAPI # 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.button['state'] = self.theme_button['state'] = tk.DISABLED
self.w.update_idletasks() self.w.update_idletasks()
@ -1086,20 +1083,20 @@ class AppWindow:
if should_return: if should_return:
logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.') logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.')
# LANG: CAPI fleetcarrier query aborted because of killswitch # 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() hotkeymgr.play_bad()
return return
if not monitor.cmdr: if not monitor.cmdr:
logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown')
# LANG: CAPI fleetcarrier query aborted because Cmdr name is 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 return
if monitor.state['GameVersion'] is None: if monitor.state['GameVersion'] is None:
logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown') logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown')
# LANG: CAPI fleetcarrier query aborted because 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 return
if not companion.session.retrying: if not companion.session.retrying:
@ -1108,7 +1105,7 @@ class AppWindow:
return return
# LANG: Status - Attempting to retrieve data from Frontier CAPI # 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() self.w.update_idletasks()
query_time = int(time()) query_time = int(time())
@ -1151,11 +1148,11 @@ class AppWindow:
# Validation # Validation
if 'name' not in capi_response.capi_data: if 'name' not in capi_response.capi_data:
# LANG: No data was returned for the fleetcarrier from the Frontier CAPI # 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'): elif not capi_response.capi_data.get('name', {}).get('callsign'):
# LANG: We didn't have the fleetcarrier callsign when we should have # 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: else:
if __debug__: # Recording if __debug__: # Recording
@ -1174,24 +1171,24 @@ class AppWindow:
elif 'commander' not in capi_response.capi_data: elif 'commander' not in capi_response.capi_data:
# This can happen with EGS Auth if no commander created yet # This can happen with EGS Auth if no commander created yet
# LANG: No data was returned for the commander from the Frontier CAPI # 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'): elif not capi_response.capi_data.get('commander', {}).get('name'):
# LANG: We didn't have the commander name when we should have # 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') elif (not capi_response.capi_data.get('lastSystem', {}).get('name')
or (capi_response.capi_data['commander'].get('docked') or (capi_response.capi_data['commander'].get('docked')
and not capi_response.capi_data.get('lastStarport', {}).get('name'))): and not capi_response.capi_data.get('lastStarport', {}).get('name'))):
# LANG: We don't know where the commander is, when we should # 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 ( elif (
not capi_response.capi_data.get('ship', {}).get('name') not capi_response.capi_data.get('ship', {}).get('name')
or not capi_response.capi_data.get('ship', {}).get('modules') or not capi_response.capi_data.get('ship', {}).get('modules')
): ):
# LANG: We don't know what ship the commander is in, when we should # 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: elif monitor.cmdr and capi_response.capi_data['commander']['name'] != monitor.cmdr:
# Companion API Commander doesn't match Journal # Companion API Commander doesn't match Journal
@ -1318,7 +1315,7 @@ class AppWindow:
except companion.ServerConnectionError as comp_err: except companion.ServerConnectionError as comp_err:
# LANG: Frontier CAPI server error when fetching data # 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}') logger.warning(f'Exception while contacting server: {comp_err}')
err = self.status['text'] = str(comp_err) err = self.status['text'] = str(comp_err)
play_bad = True 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 # We need to 'close' the auth else it'll see STATE_OK and think login() isn't needed
companion.session.reinit_session() companion.session.reinit_session()
# LANG: Frontier CAPI Access Token expired, trying to get a new one # 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(): if companion.session.login():
logger.debug('Initial query failed, but login() just worked, trying again...') logger.debug('Initial query failed, but login() just worked, trying again...')
companion.session.retrying = True companion.session.retrying = True
@ -1366,7 +1363,7 @@ class AppWindow:
if not err: # not self.status['text']: # no errors if not err: # not self.status['text']: # no errors
# LANG: Time when we last obtained Frontier CAPI data # 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: if capi_response.play_sound and play_bad:
hotkeymgr.play_bad() hotkeymgr.play_bad()
@ -1394,9 +1391,9 @@ class AppWindow:
return { return {
None: '', None: '',
'Idle': '', 'Idle': '',
'FighterCon': _('Fighter'), # LANG: Multicrew role 'FighterCon': tr.tl('Fighter'), # LANG: Multicrew role
'FireCon': _('Gunner'), # LANG: Multicrew role 'FireCon': tr.tl('Gunner'), # LANG: Multicrew role
'FlightCon': _('Helm'), # LANG: Multicrew role 'FlightCon': tr.tl('Helm'), # LANG: Multicrew role
}.get(role, role) }.get(role, role)
if monitor.thread is None: if monitor.thread is None:
@ -1419,7 +1416,7 @@ class AppWindow:
else: else:
self.cmdr['text'] = f'{monitor.cmdr}' 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) self.ship.configure(state=tk.NORMAL, text=crewroletext(monitor.state['Role']), url=None)
elif monitor.cmdr: elif monitor.cmdr:
@ -1429,7 +1426,7 @@ class AppWindow:
else: else:
self.cmdr['text'] = monitor.cmdr 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 # TODO: Show something else when on_foot
if monitor.state['ShipName']: if monitor.state['ShipName']:
@ -1452,7 +1449,7 @@ class AppWindow:
else: else:
self.cmdr['text'] = '' 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'] = '' self.ship['text'] = ''
if monitor.cmdr and monitor.is_beta: if monitor.cmdr and monitor.is_beta:
@ -1589,7 +1586,7 @@ class AppWindow:
try: try:
companion.session.auth_callback() companion.session.auth_callback()
# LANG: Successfully authenticated with the Frontier website # 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(0, state=tk.NORMAL) # Status
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
@ -1675,10 +1672,10 @@ class AppWindow:
# Update button in main window # Update button in main window
cooldown_time = int(self.capi_query_holdoff_time - time()) cooldown_time = int(self.capi_query_holdoff_time - time())
# LANG: Cooldown on 'Update' button # 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) self.w.after(1000, self.cooldown)
else: 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'] = ( self.button['state'] = self.theme_button['state'] = (
monitor.cmdr and monitor.cmdr and
monitor.mode and monitor.mode and
@ -1743,7 +1740,7 @@ class AppWindow:
self.parent = parent self.parent = parent
# LANG: Help > About App # LANG: Help > About App
self.title(_('About {APP}').format(APP=applongname)) self.title(tr.tl('About {APP}').format(APP=applongname))
if parent.winfo_viewable(): if parent.winfo_viewable():
self.transient(parent) 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.config(state=tk.DISABLED, bg=frame.cget("background"), font="TkDefaultFont")
self.appversion_label.grid(row=row, column=0, sticky=tk.E) self.appversion_label.grid(row=row, column=0, sticky=tk.E)
# LANG: Help > Release Notes # 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/' url='https://github.com/EDCD/EDMarketConnector/releases/tag/Release/'
f'{appversion_nobuild()}', f'{appversion_nobuild()}',
underline=True) underline=True)
@ -1806,7 +1803,7 @@ class AppWindow:
ttk.Label(frame).grid(row=row, column=0) # spacer ttk.Label(frame).grid(row=row, column=0) # spacer
row += 1 row += 1
# LANG: Generic 'OK' button label # 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.grid(row=row, column=2, sticky=tk.E)
button.bind("<Return>", lambda event: self.apply()) button.bind("<Return>", lambda event: self.apply())
self.protocol("WM_DELETE_WINDOW", self._destroy) self.protocol("WM_DELETE_WINDOW", self._destroy)
@ -1874,7 +1871,7 @@ class AppWindow:
# Let the user know we're shutting down. # Let the user know we're shutting down.
# LANG: The application is shutting down # LANG: The application is shutting down
self.status['text'] = _('Shutting down...') self.status['text'] = tr.tl('Shutting down...')
self.w.update_idletasks() self.w.update_idletasks()
logger.info('Starting shutdown procedures...') logger.info('Starting shutdown procedures...')
@ -2059,10 +2056,10 @@ def validate_providers():
return return
# LANG: Popup-text about Reset Providers # 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: for provider in reset_providers:
# LANG: Text About What Provider Was Reset # 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( popup_text = popup_text.format(
PROVIDER=provider, PROVIDER=provider,
OLDPROV=reset_providers[provider][0], OLDPROV=reset_providers[provider][0],
@ -2074,7 +2071,7 @@ def validate_providers():
tk.messagebox.showinfo( tk.messagebox.showinfo(
# LANG: Popup window title for Reset Providers # LANG: Popup window title for Reset Providers
_('EDMC: Default Providers Reset'), tr.tl('EDMC: Default Providers Reset'),
popup_text popup_text
) )
@ -2200,7 +2197,7 @@ sys.path: {sys.path}'''
# Plain, not via `logger` # Plain, not via `logger`
print(f'{applongname} {appversion()}') 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) setup_killswitches(args.killswitches_file)
@ -2235,7 +2232,7 @@ sys.path: {sys.path}'''
"""Display message about 'broken' plugins that failed to load.""" """Display message about 'broken' plugins that failed to load."""
if plug.PLUGINS_broken: if plug.PLUGINS_broken:
# LANG: Popup-text about 'broken' plugins that failed to load # 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}' " "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 " "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 " 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. # Substitute in the other words.
popup_text = popup_text.format( popup_text = popup_text.format(
PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab
FILE=_('File'), # LANG: 'File' menu FILE=tr.tl('File'), # LANG: 'File' menu
SETTINGS=_('Settings'), # LANG: File > Settings SETTINGS=tr.tl('Settings'), # LANG: File > Settings
DISABLED='.disabled' DISABLED='.disabled'
) )
# And now we do need these to be actual \r\n # And now we do need these to be actual \r\n
@ -2255,7 +2252,7 @@ sys.path: {sys.path}'''
tk.messagebox.showinfo( tk.messagebox.showinfo(
# LANG: Popup window title for list of 'broken' plugins that failed to load # LANG: Popup window title for list of 'broken' plugins that failed to load
_('EDMC: Broken Plugins'), tr.tl('EDMC: Broken Plugins'),
popup_text popup_text
) )
@ -2264,7 +2261,7 @@ sys.path: {sys.path}'''
plugins_not_py3_last = config.get_int('plugins_not_py3_last', default=0) 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: if (plugins_not_py3_last + 86400) < int(time()) and plug.PLUGINS_not_py3:
# LANG: Popup-text about 'active' plugins without Python 3.x support # 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 " "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 " "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 " "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. # Substitute in the other words.
popup_text = popup_text.format( popup_text = popup_text.format(
PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab
FILE=_('File'), # LANG: 'File' menu FILE=tr.tl('File'), # LANG: 'File' menu
SETTINGS=_('Settings'), # LANG: File > Settings SETTINGS=tr.tl('Settings'), # LANG: File > Settings
DISABLED='.disabled' DISABLED='.disabled'
) )
# And now we do need these to be actual \r\n # And now we do need these to be actual \r\n
@ -2285,7 +2282,7 @@ sys.path: {sys.path}'''
tk.messagebox.showinfo( tk.messagebox.showinfo(
# LANG: Popup window title for list of 'enabled' plugins that don't work with Python 3.x # 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 popup_text
) )
config.set('plugins_not_py3_last', int(time())) config.set('plugins_not_py3_last', int(time()))
@ -2298,7 +2295,7 @@ sys.path: {sys.path}'''
if fdevid_file.is_file(): if fdevid_file.is_file():
continue continue
# LANG: Popup-text about missing FDEVID Files # LANG: Popup-text about missing FDEVID Files
popup_text = _( popup_text = tr.tl(
"FDevID Files not found! Some functionality regarding commodities " "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 " r"may be disabled.\r\n\r\n Do you want to open the Wiki page on "
"how to set up submodules?" "how to set up submodules?"
@ -2309,7 +2306,7 @@ sys.path: {sys.path}'''
openwikipage = tk.messagebox.askquestion( openwikipage = tk.messagebox.askquestion(
# LANG: Popup window title for missing FDEVID files # LANG: Popup window title for missing FDEVID files
_('FDevIDs: Missing Commodity Files'), tr.tl('FDevIDs: Missing Commodity Files'),
popup_text popup_text
) )
if openwikipage == "yes": if openwikipage == "yes":

View File

@ -37,12 +37,11 @@ from config import config, user_agent
from edmc_data import companion_category_map as category_map from edmc_data import companion_category_map as category_map
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from monitor import monitor from monitor import monitor
from l10n import translations as tr
logger = get_main_logger() logger = get_main_logger()
if TYPE_CHECKING: if TYPE_CHECKING:
def _(x: str): return x
UserDict = collections.UserDict[str, Any] # indicate to our type checkers what this generic class holds normally UserDict = collections.UserDict[str, Any] # indicate to our type checkers what this generic class holds normally
else: else:
UserDict = collections.UserDict # Otherwise simply use the actual class UserDict = collections.UserDict # Otherwise simply use the actual class
@ -224,7 +223,7 @@ class ServerError(Exception):
self.args = args self.args = args
if not args: if not args:
# LANG: Frontier CAPI didn't respond # 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): class ServerConnectionError(ServerError):
@ -243,7 +242,7 @@ class ServerLagging(Exception):
self.args = args self.args = args
if not args: if not args:
# LANG: Frontier CAPI data doesn't agree with latest Journal game location # 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): class NoMonitorStation(Exception):
@ -259,7 +258,7 @@ class NoMonitorStation(Exception):
self.args = args self.args = args
if not args: if not args:
# LANG: Commander is docked at an EDO settlement, got out and back in, we forgot the station # 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): class CredentialsError(Exception):
@ -269,7 +268,7 @@ class CredentialsError(Exception):
self.args = args self.args = args
if not args: if not args:
# LANG: Generic "something went wrong with Frontier Auth" error # LANG: Generic "something went wrong with Frontier Auth" error
self.args = (_('Error: Invalid Credentials'),) self.args = (tr.tl('Error: Invalid Credentials'),)
class CredentialsRequireRefresh(Exception): class CredentialsRequireRefresh(Exception):
@ -294,7 +293,7 @@ class CmdrError(Exception):
self.args = args self.args = args
if not args: if not args:
# LANG: Frontier CAPI authorisation not for currently game-active commander # LANG: Frontier CAPI authorisation not for currently game-active commander
self.args = (_('Error: Wrong Cmdr'),) self.args = (tr.tl('Error: Wrong Cmdr'),)
class Auth: class Auth:
@ -429,7 +428,7 @@ class Auth:
'<unknown error>' '<unknown error>'
) )
# LANG: Generic error prefix - following text is from Frontier auth service # 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 r = None
try: try:
@ -472,18 +471,18 @@ class Auth:
if (usr := data_decode.get('usr')) is None: if (usr := data_decode.get('usr')) is None:
logger.error('No "usr" in /decode data') logger.error('No "usr" in /decode data')
# LANG: Frontier auth, no 'usr' section in returned 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: if (customer_id := usr.get('customer_id')) is None:
logger.error('No "usr"->"customer_id" in /decode data') logger.error('No "usr"->"customer_id" in /decode data')
# LANG: Frontier auth, no 'customer_id' in 'usr' section in returned 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<id>' # All 'FID' seen in Journals so far have been 'F<id>'
# Frontier, Steam and Epic # Frontier, Steam and Epic
if f'F{customer_id}' != monitor.state.get('FID'): if f'F{customer_id}' != monitor.state.get('FID'):
# LANG: Frontier auth customer_id doesn't match game session 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}\"') logger.info(f'Frontier CAPI Auth: New token for \"{self.cmdr}\"')
cmdrs = config.get_list('cmdrs', default=[]) cmdrs = config.get_list('cmdrs', default=[])
@ -505,7 +504,7 @@ class Auth:
self.dump(r) self.dump(r)
# LANG: Failed to get Access Token from Frontier Auth service # 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}\"") logger.error(f"Frontier CAPI Auth: Can't get token for \"{self.cmdr}\"")
self.dump(r) self.dump(r)
@ -514,7 +513,7 @@ class Auth:
'<unknown error>' '<unknown error>'
) )
# LANG: Generic error prefix - following text is from Frontier auth service # 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 @staticmethod
def invalidate(cmdr: str | None) -> None: def invalidate(cmdr: str | None) -> None:
@ -841,7 +840,7 @@ class Session:
except Exception as e: except Exception as e:
logger.debug('Attempting GET', exc_info=e) logger.debug('Attempting GET', exc_info=e)
# LANG: Frontier CAPI data retrieval failed # 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: if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data:
logger.error('No commander in returned data') logger.error('No commander in returned data')
@ -874,7 +873,7 @@ class Session:
if response.status_code == 418: if response.status_code == 418:
# "I'm a teapot" - used to signal maintenance # "I'm a teapot" - used to signal maintenance
# LANG: Frontier CAPI returned 418, meaning down for 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') logger.exception('Frontier CAPI: Misc. Error')
raise ServerError('Frontier CAPI: Misc. Error') raise ServerError('Frontier CAPI: Misc. Error')

View File

@ -13,17 +13,13 @@ import tkinter as tk
from enum import Enum from enum import Enum
from os import getpid as os_getpid from os import getpid as os_getpid
from tkinter import ttk 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 config import config
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
logger = get_main_logger() logger = get_main_logger()
if TYPE_CHECKING: # pragma: no cover
def _(x: str) -> str:
return x
class JournalLockResult(Enum): class JournalLockResult(Enum):
"""Enumeration of possible outcomes of trying to lock the Journal Directory.""" """Enumeration of possible outcomes of trying to lock the Journal Directory."""
@ -212,7 +208,7 @@ class JournalLock:
self.parent = parent self.parent = parent
self.callback = callback self.callback = callback
# LANG: Title text on popup when Journal directory already locked # 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 # remove decoration
if sys.platform == 'win32': if sys.platform == 'win32':
@ -225,16 +221,17 @@ class JournalLock:
self.blurb = tk.Label(frame) self.blurb = tk.Label(frame)
# LANG: Text for when newly selected Journal directory is already locked # LANG: Text for when newly selected Journal directory is already locked
self.blurb['text'] = _("The new Journal Directory location is already locked.{CR}" 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.") "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) self.blurb.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW)
# LANG: Generic 'Retry' button label # 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) self.retry_button.grid(row=2, column=0, sticky=tk.EW)
# LANG: Generic 'Ignore' button label # 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.ignore_button.grid(row=2, column=1, sticky=tk.EW)
self.protocol("WM_DELETE_WINDOW", self._destroy) self.protocol("WM_DELETE_WINDOW", self._destroy)

47
l10n.py
View File

@ -23,9 +23,6 @@ from typing import TYPE_CHECKING, Iterable, TextIO, cast
from config import config from config import config
from EDMCLogging import get_main_logger 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 # Note that this is also done in EDMarketConnector.py, and thus removing this here may not have a desired effect
try: try:
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
@ -61,7 +58,16 @@ if sys.platform == 'win32':
GetNumberFormatEx.restype = ctypes.c_int 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 = 'en' # strings in this code are in English
FALLBACK_NAME = 'English' FALLBACK_NAME = 'English'
@ -79,6 +85,8 @@ class _Translations:
Use when translation is not desired or not available Use when translation is not desired or not available
""" """
self.translations = {None: {}} 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') builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n')
def install(self, lang: str | None = None) -> None: # noqa: CCR001 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 :param lang: The language to translate to, defaults to the preferred language
""" """
available = self.available() available = self.available()
available.add(_Translations.FALLBACK) available.add(Translations.FALLBACK)
if not lang: if not lang:
# Choose the default language # Choose the default language
for preferred in Locale.preferred_languages(): for preferred in Locale.preferred_languages():
@ -122,6 +130,8 @@ class _Translations:
except Exception: except Exception:
logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}') 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 builtins.__dict__['_'] = self.translate
def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
@ -135,12 +145,12 @@ class _Translations:
for line in h: for line in h:
if line.strip(): if line.strip():
match = _Translations.TRANS_RE.match(line) match = Translations.TRANS_RE.match(line)
if match: if match:
to_set = match.group(2).replace(r'\"', '"').replace('{CR}', '\n') to_set = match.group(2).replace(r'\"', '"').replace('{CR}', '\n')
translations[match.group(1).replace(r'\"', '"')] = to_set 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()}') logger.debug(f'Bad translation: {line.strip()}')
h.close() h.close()
@ -149,6 +159,10 @@ class _Translations:
return 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: def translate(self, x: str, context: str | None = None) -> str:
""" """
Translate the given string to the current lang. Translate the given string to the current lang.
@ -182,11 +196,11 @@ class _Translations:
"""Available language names by code.""" """Available language names by code."""
names: dict[str | None, str] = { names: dict[str | None, str] = {
# LANG: The system default language choice in Settings > Appearance # 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( names.update(sorted(
[(lang, self.contents(lang).get(LANGUAGE_ID, lang)) for lang in self.available()] + [(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] key=lambda x: x[1]
)) # Sort by name )) # Sort by name
@ -324,9 +338,22 @@ class _Locale:
# singletons # singletons
Locale = _Locale() 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 # generate template strings file - like xgettext
# parsing is limited - only single ' or " delimited strings, and only one string per line # parsing is limited - only single ' or " delimited strings, and only one string per line
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -34,10 +34,6 @@ STARTUP = 'journal.startup'
MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds
MAX_FCMATERIALS_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': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR

View File

@ -27,15 +27,11 @@ import io
import json import json
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING
import myNotebook as nb # noqa: N813 # its not my fault. import myNotebook as nb # noqa: N813 # its not my fault.
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from plug import show_error from plug import show_error
from config import config from config import config
from l10n import translations as tr
if TYPE_CHECKING:
def _(s: str) -> str:
...
class CoriolisConfig: class CoriolisConfig:
@ -45,15 +41,15 @@ class CoriolisConfig:
self.normal_url = '' self.normal_url = ''
self.beta_url = '' self.beta_url = ''
self.override_mode = '' self.override_mode = ''
self.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto self.override_text_old_auto = tr.tl('Auto') # LANG: Coriolis normal/beta selection - auto
self.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal self.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal
self.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta self.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta
self.normal_textvar = tk.StringVar() self.normal_textvar = tk.StringVar()
self.beta_textvar = tk.StringVar() self.beta_textvar = tk.StringVar()
self.override_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.""" """Initialize Coriolis URLs and override mode from configuration."""
self.normal_url = config.get_str('coriolis_normal_url', default=DEFAULT_NORMAL_URL) 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) 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.beta_textvar.set(value=self.beta_url)
self.override_textvar.set( self.override_textvar.set(
value={ value={
'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection 'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection
'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection 'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection
'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection 'beta': tr.tl('Beta') # LANG: 'Beta' label for Coriolis site override selection
}.get(self.override_mode, _('Auto')) # LANG: 'Auto' 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 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 # 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_auto = tr.tl('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_normal = tr.tl('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_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta
conf_frame = nb.Frame(parent) conf_frame = nb.Frame(parent)
conf_frame.columnconfigure(index=1, weight=1) conf_frame.columnconfigure(index=1, weight=1)
cur_row = 0 cur_row = 0
# LANG: Settings>Coriolis: Help/hint for changing coriolis URLs # 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='" "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) )).grid(sticky=tk.EW, row=cur_row, column=0, padx=PADX, pady=PADY, columnspan=3)
cur_row += 1 cur_row += 1
# LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL # 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( nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid(
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
) )
# LANG: Generic 'Reset' button label # 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( command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
) )
cur_row += 1 cur_row += 1
# LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL # 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( nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
) )
# LANG: Generic 'Reset' button label # 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( command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0 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. # 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 # 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 sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY
) )
nb.OptionMenu( nb.OptionMenu(
conf_frame, conf_frame,
coriolis_config.override_textvar, coriolis_config.override_textvar,
coriolis_config.override_textvar.get(), coriolis_config.override_textvar.get(),
_('Normal'), # LANG: 'Normal' label for Coriolis site override selection tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection
_('Beta'), # LANG: 'Beta' label for Coriolis site override selection tr.tl('Beta'), # LANG: 'Beta' label for Coriolis site override selection
_('Auto') # LANG: 'Auto' 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) ).grid(sticky=tk.W, row=cur_row, column=1, padx=PADX, pady=BOXY)
cur_row += 1 cur_row += 1
@ -159,9 +155,9 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None:
# Convert to unlocalised names # Convert to unlocalised names
coriolis_config.override_mode = { coriolis_config.override_mode = {
_('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal tr.tl('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal
_('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta tr.tl('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta
_('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto tr.tl('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto
}.get(coriolis_config.override_mode, coriolis_config.override_mode) }.get(coriolis_config.override_mode, coriolis_config.override_mode)
# Check if the language was changed and the override_mode was valid before the change # 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'): if coriolis_config.override_mode in ('beta', 'normal', 'auto'):
coriolis_config.override_textvar.set( coriolis_config.override_textvar.set(
value={ value={
'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection 'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection
'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection 'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection
'beta': _('Beta') # LANG: 'Beta' 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 # 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 the override mode is still invalid, default to auto
if coriolis_config.override_mode not in ('beta', 'normal', 'auto'): if coriolis_config.override_mode not in ('beta', 'normal', 'auto'):
logger.warning(f'Unexpected value {coriolis_config.override_mode=!r}. Defaulting to "auto"') logger.warning(f'Unexpected value {coriolis_config.override_mode=!r}. Defaulting to "auto"')
coriolis_config.override_mode = '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_normal_url', coriolis_config.normal_url)
config.set('coriolis_beta_url', coriolis_config.beta_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: def _get_target_url(is_beta: bool) -> str:
if coriolis_config.override_mode not in ('auto', 'normal', 'beta'): if coriolis_config.override_mode not in ('auto', 'normal', 'beta'):
# LANG: Settings>Coriolis - invalid override mode found # 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!') logger.warning(f'Unexpected override mode {coriolis_config.override_mode!r}! defaulting to auto!')
coriolis_config.override_mode = 'auto' coriolis_config.override_mode = 'auto'
if coriolis_config.override_mode == 'beta': if coriolis_config.override_mode == 'beta':

View File

@ -31,13 +31,7 @@ import tkinter as tk
from platform import system from platform import system
from textwrap import dedent from textwrap import dedent
from threading import Lock from threading import Lock
from typing import ( from typing import Any, Iterator, Mapping, MutableMapping
TYPE_CHECKING,
Any,
Iterator,
Mapping,
MutableMapping,
)
import requests import requests
import companion import companion
import edmc_data import edmc_data
@ -52,10 +46,7 @@ from myNotebook import Frame
from prefs import prefsVersion from prefs import prefsVersion
from ttkHyperlinkLabel import HyperlinkLabel from ttkHyperlinkLabel import HyperlinkLabel
from util import text from util import text
from l10n import translations as tr
if TYPE_CHECKING:
def _(x: str) -> str:
return x
logger = get_main_logger() logger = get_main_logger()
@ -441,7 +432,7 @@ class EDDNSender:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.debug('Failed sending', exc_info=e) logger.debug('Failed sending', exc_info=e)
# LANG: Error while trying to send data to EDDN # 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: except Exception as e:
logger.debug('Failed sending', exc_info=e) logger.debug('Failed sending', exc_info=e)
@ -566,17 +557,17 @@ class EDDNSender:
if status_code == 429: # HTTP UPGRADE REQUIRED if status_code == 429: # HTTP UPGRADE REQUIRED
logger.warning('EDMC is sending schemas that are too old') logger.warning('EDMC is sending schemas that are too old')
# LANG: EDDN has banned this version of our client # 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: if status_code == 400:
# we a validation check or something else. # we a validation check or something else.
logger.warning(f'EDDN Error: {status_code} -- {exception.response}') logger.warning(f'EDDN Error: {status_code} -- {exception.response}')
# LANG: EDDN returned an error that indicates something about what we sent it was wrong # 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}') 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 # 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. # 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( this.eddn_station_button = nb.Checkbutton(
eddnframe, eddnframe,
# LANG: Enable EDDN support for station data checkbox label # 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, variable=this.eddn_station,
command=prefsvarchanged command=prefsvarchanged
) # Output setting ) # Output setting
@ -2201,7 +2192,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame:
this.eddn_system_button = nb.Checkbutton( this.eddn_system_button = nb.Checkbutton(
eddnframe, eddnframe,
# LANG: Enable EDDN support for system and other scan data checkbox label # 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, variable=this.eddn_system,
command=prefsvarchanged command=prefsvarchanged
) )
@ -2213,7 +2204,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame:
this.eddn_delay_button = nb.Checkbutton( this.eddn_delay_button = nb.Checkbutton(
eddnframe, eddnframe,
# LANG: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this # 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 variable=this.eddn_delay
) )
this.eddn_delay_button.grid(row=cur_row, padx=BUTTONX, pady=PADY, sticky=tk.W) 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) should_return, new_data = killswitch.check_killswitch('plugins.eddn.journal', entry)
if should_return: 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 return None
should_return, new_data = killswitch.check_killswitch(f'plugins.eddn.journal.event.{entry["event"]}', new_data) 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: except requests.exceptions.RequestException as e:
logger.debug('Failed in send_message', exc_info=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: except Exception as e:
logger.debug('Failed in export_journal_generic', exc_info=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: except requests.exceptions.RequestException as e:
logger.debug(f'Failed exporting {entry["event"]}', exc_info=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: except Exception as e:
logger.debug(f'Failed exporting {entry["event"]}', exc_info=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") status = this.parent.nametowidget(f".{appname.lower()}.status")
old_status = status['text'] old_status = status['text']
if not old_status: 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() status.update_idletasks()
this.eddn.export_commodities(data, is_beta) 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: except requests.RequestException as e:
logger.debug('Failed exporting data', exc_info=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: except Exception as e:
logger.debug('Failed exporting data', exc_info=e) logger.debug('Failed exporting data', exc_info=e)

View File

@ -28,7 +28,7 @@ from queue import Queue
from threading import Thread from threading import Thread
from time import sleep from time import sleep
from tkinter import ttk 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 requests
import killswitch import killswitch
import monitor 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 edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from ttkHyperlinkLabel import HyperlinkLabel from ttkHyperlinkLabel import HyperlinkLabel
from l10n import translations as tr
if TYPE_CHECKING:
def _(x: str) -> str:
return x
# TODO: # TODO:
# 1) Re-factor EDSM API calls out of journal_entry() into own function. # 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 = tk.IntVar(value=config.get_int('edsm_out') and 1)
this.log_button = nb.Checkbutton( this.log_button = nb.Checkbutton(
frame, 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, variable=this.log,
command=prefsvarchanged command=prefsvarchanged
) )
@ -328,7 +327,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
this.label = HyperlinkLabel( this.label = HyperlinkLabel(
frame, 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'), background=nb.Label().cget('background'),
url='https://www.edsm.net/settings/api', url='https://www.edsm.net/settings/api',
underline=True underline=True
@ -336,21 +335,21 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
if this.label: if this.label:
this.label.grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W) this.label.grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W)
cur_row += 1 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_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
this.cmdr_text = nb.Label(frame) this.cmdr_text = nb.Label(frame)
this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.W) this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.W)
cur_row += 1 cur_row += 1
# LANG: EDSM Commander name label in EDSM settings # 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_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
this.user = nb.EntryMenu(frame) this.user = nb.EntryMenu(frame)
this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
cur_row += 1 cur_row += 1
# LANG: EDSM API key label # 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_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey = nb.EntryMenu(frame, show="*", width=50)
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) 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( show_password_checkbox = nb.Checkbutton(
frame, 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, variable=show_password_var,
command=toggle_password_visibility command=toggle_password_visibility
) )
@ -398,7 +397,7 @@ def prefs_cmdr_changed(cmdr: str | None, is_beta: bool) -> None: # noqa: CCR001
else: else:
if this.cmdr_text: if this.cmdr_text:
# LANG: We have no data on the current commander # 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 to_set: Literal['normal'] | Literal['disabled'] = tk.DISABLED
if cmdr and not is_beta and this.log and this.log.get(): 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) should_return, new_entry = killswitch.check_killswitch('plugins.edsm.journal', entry, logger)
if should_return: if should_return:
# LANG: EDSM plugin - Journal handling disabled by killswitch # 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 '' return ''
should_return, new_entry = killswitch.check_killswitch( 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 # LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data
logger.info("EDSM only accepts Live galaxy data") logger.info("EDSM only accepts Live galaxy data")
this.legacy_galaxy_last_notified = datetime.now(timezone.utc) 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 '' return ''
@ -778,7 +777,7 @@ def send_to_edsm( # noqa: CCR001
if msg_num // 100 == 2: if msg_num // 100 == 2:
logger.warning(f'EDSM\t{msg_num} {msg}\t{json.dumps(pending, separators=(",", ": "))}') logger.warning(f'EDSM\t{msg_num} {msg}\t{json.dumps(pending, separators=(",", ": "))}')
# LANG: EDSM Plugin - Error message from EDSM API # 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: else:
if msg_num // 100 == 1: if msg_num // 100 == 1:
logger.trace_if('plugin.edsm.api', 'Overall OK') logger.trace_if('plugin.edsm.api', 'Overall OK')
@ -944,7 +943,7 @@ def worker() -> None: # noqa: CCR001 C901
else: else:
# LANG: EDSM Plugin - Error connecting to EDSM API # 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'): if entry['event'].lower() in ('shutdown', 'commander', 'fileheader'):
# Game shutdown or new login, so we MUST not hang on to pending # Game shutdown or new login, so we MUST not hang on to pending
pending = [] pending = []
@ -1018,11 +1017,11 @@ def edsm_notify_system(reply: Mapping[str, Any]) -> None:
if not reply: if not reply:
this.system_link['image'] = this._IMG_ERROR this.system_link['image'] = this._IMG_ERROR
# LANG: EDSM Plugin - Error connecting to EDSM API # 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): elif reply['msgnum'] // 100 not in (1, 4):
this.system_link['image'] = this._IMG_ERROR this.system_link['image'] = this._IMG_ERROR
# LANG: EDSM Plugin - Error message from EDSM API # 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'): elif reply.get('systemCreated'):
this.system_link['image'] = this._IMG_NEW this.system_link['image'] = this._IMG_NEW
else: else:

View File

@ -30,7 +30,7 @@ from datetime import datetime, timedelta, timezone
from operator import itemgetter from operator import itemgetter
from threading import Lock, Thread from threading import Lock, Thread
from tkinter import ttk 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 requests
import edmc_data import edmc_data
import killswitch import killswitch
@ -42,13 +42,10 @@ from config import applongname, appname, appversion, config, debug_senders
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from monitor import monitor from monitor import monitor
from ttkHyperlinkLabel import HyperlinkLabel from ttkHyperlinkLabel import HyperlinkLabel
from l10n import translations as tr
logger = get_main_logger() logger = get_main_logger()
if TYPE_CHECKING:
def _(x: str) -> str:
return x
_TIMEOUT = 20 _TIMEOUT = 20
FAKE = ('CQC', 'Training', 'Destination') # Fake systems that shouldn't be sent to Inara 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 = tk.IntVar(value=config.get_int('inara_out') and 1)
this.log_button = nb.Checkbutton( this.log_button = nb.Checkbutton(
frame, 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, variable=this.log,
command=prefsvarchanged command=prefsvarchanged
) )
@ -280,7 +277,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
# Section heading in settings # Section heading in settings
this.label = HyperlinkLabel( this.label = HyperlinkLabel(
frame, 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'), background=nb.Label().cget('background'),
url='https://inara.cz/settings-api', url='https://inara.cz/settings-api',
underline=True underline=True
@ -290,7 +287,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
cur_row += 1 cur_row += 1
# LANG: Inara API key label # 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_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
this.apikey = nb.EntryMenu(frame, show="*", width=50) this.apikey = nb.EntryMenu(frame, show="*", width=50)
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW) 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_var.set(False) # Password is initially masked
show_password_checkbox = nb.Checkbutton( show_password_checkbox = nb.Checkbutton(
frame, 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, variable=show_password_var,
command=toggle_password_visibility, 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) should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger)
if should_return: 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') logger.trace('returning due to killswitch match')
return '' 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)) 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 # 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) 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 '' 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.warning(f'Inara\t{status} {error_message}')
logger.debug(f'JSON data:\n{json.dumps(data, indent=2, separators = (",", ": "))}') 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}) # 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: 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: if reply_status // 100 != 2:
# LANG: INARA API returned some kind of error (error message will be contained in {MSG}) # 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}' MSG=f'{data_event["eventName"]}, {reply_text}'
)) ))

138
prefs.py
View File

@ -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 colorchooser as tkColorChooser # type: ignore # noqa: N812
from tkinter import ttk from tkinter import ttk
from types import TracebackType 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 myNotebook as nb # noqa: N813
import plug import plug
from config import appversion_nobuild, config from config import appversion_nobuild, config
from EDMCLogging import edmclogger, get_main_logger from EDMCLogging import edmclogger, get_main_logger
from constants import appname from constants import appname
from hotkey import hotkeymgr from hotkey import hotkeymgr
from l10n import Translations from l10n import translations as tr
from monitor import monitor from monitor import monitor
from theme import theme from theme import theme
from ttkHyperlinkLabel import HyperlinkLabel from ttkHyperlinkLabel import HyperlinkLabel
logger = get_main_logger() logger = get_main_logger()
if TYPE_CHECKING:
def _(x: str) -> str:
return x
# TODO: Decouple this from platform as far as possible # TODO: Decouple this from platform as far as possible
@ -224,7 +220,7 @@ class PreferencesDialog(tk.Toplevel):
self.parent = parent self.parent = parent
self.callback = callback self.callback = callback
# LANG: File > Settings (macOS) # LANG: File > Settings (macOS)
self.title(_('Settings')) self.title(tr.tl('Settings'))
if parent.winfo_viewable(): if parent.winfo_viewable():
self.transient(parent) self.transient(parent)
@ -270,7 +266,7 @@ class PreferencesDialog(tk.Toplevel):
buttonframe.columnconfigure(0, weight=1) buttonframe.columnconfigure(0, weight=1)
ttk.Label(buttonframe).grid(row=0, column=0) # spacer ttk.Label(buttonframe).grid(row=0, column=0) # spacer
# LANG: 'OK' button on Settings/Preferences window # 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.grid(row=0, column=1, sticky=tk.E)
button.bind("<Return>", lambda event: self.apply()) button.bind("<Return>", lambda event: self.apply())
self.protocol("WM_DELETE_WINDOW", self._destroy) self.protocol("WM_DELETE_WINDOW", self._destroy)
@ -313,13 +309,13 @@ class PreferencesDialog(tk.Toplevel):
row = AutoInc(start=0) row = AutoInc(start=0)
# LANG: Settings > Output - choosing what data to save to files # 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_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 = tk.IntVar(value=1 if (output & config.OUT_MKT_CSV) else 0)
self.out_csv_button = nb.Checkbutton( self.out_csv_button = nb.Checkbutton(
output_frame, 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, variable=self.out_csv,
command=self.outvarchanged 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 = tk.IntVar(value=1 if (output & config.OUT_MKT_TD) else 0)
self.out_td_button = nb.Checkbutton( self.out_td_button = nb.Checkbutton(
output_frame, 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, variable=self.out_td,
command=self.outvarchanged command=self.outvarchanged
) )
@ -338,7 +334,7 @@ class PreferencesDialog(tk.Toplevel):
# Output setting # Output setting
self.out_ship_button = nb.Checkbutton( self.out_ship_button = nb.Checkbutton(
output_frame, output_frame,
text=_('Ship loadout'), # LANG: Settings > Output option text=tr.tl('Ship loadout'), # LANG: Settings > Output option
variable=self.out_ship, variable=self.out_ship,
command=self.outvarchanged command=self.outvarchanged
) )
@ -348,7 +344,7 @@ class PreferencesDialog(tk.Toplevel):
# Output setting # Output setting
self.out_auto_button = nb.Checkbutton( self.out_auto_button = nb.Checkbutton(
output_frame, 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, variable=self.out_auto,
command=self.outvarchanged command=self.outvarchanged
) )
@ -357,14 +353,14 @@ class PreferencesDialog(tk.Toplevel):
self.outdir = tk.StringVar() self.outdir = tk.StringVar()
self.outdir.set(str(config.get_str('outdir'))) self.outdir.set(str(config.get_str('outdir')))
# LANG: Settings > Output - Label for "where files are located" # 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 # 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_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 = ttk.Entry(output_frame, takefocus=False)
self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) 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( self.outbutton = ttk.Button(
output_frame, output_frame,
@ -372,12 +368,12 @@ class PreferencesDialog(tk.Toplevel):
# Technically this is different from the label in Settings > Output, as *this* is used # Technically this is different from the label in Settings > Output, as *this* is used
# as the title of the popup folder selection window. # as the title of the popup folder selection window.
# LANG: Settings > Output - Label for "where files are located" # 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()) self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get())
# LANG: Label for 'Output' Settings/Preferences tab # 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: def __setup_plugin_tabs(self, notebook: ttk.Notebook) -> None:
for plugin in plug.PLUGINS: for plugin in plug.PLUGINS:
@ -403,19 +399,19 @@ class PreferencesDialog(tk.Toplevel):
nb.Label( nb.Label(
config_frame, config_frame,
# LANG: Settings > Configuration - Label for Journal files location # 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()) ).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()) 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: with row as cur_row:
self.logbutton = ttk.Button( self.logbutton = ttk.Button(
config_frame, config_frame,
text=text, text=text,
# LANG: Settings > Configuration - Label for Journal files location # 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) 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( ttk.Button(
config_frame, config_frame,
# LANG: Settings > Configuration - Label on 'reset journal files location to default' button # LANG: Settings > Configuration - Label on 'reset journal files location to default' button
text=_('Default'), text=tr.tl('Default'),
command=self.logdir_reset, command=self.logdir_reset,
state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED 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) ).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( nb.Label(
config_frame, 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()) ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
nb.Checkbutton( nb.Checkbutton(
config_frame, config_frame,
# LANG: Configuration - Enable or disable the Fleet Carrier CAPI calls # 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 variable=self.capi_fleetcarrier
).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) ).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: with row as cur_row:
nb.Label( nb.Label(
config_frame, 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) ).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) 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 # No hotkey/shortcut currently defined
# TODO: display Only shows up on windows # TODO: display Only shows up on windows
# LANG: No hotkey/shortcut set # 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('<FocusIn>', self.hotkeystart) self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
@ -480,7 +476,7 @@ class PreferencesDialog(tk.Toplevel):
self.hotkey_only_btn = nb.Checkbutton( self.hotkey_only_btn = nb.Checkbutton(
config_frame, config_frame,
# LANG: Configuration - Act on hotkey only when ED is in foreground # 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, variable=self.hotkey_only,
state=tk.NORMAL if self.hotkey_code else tk.DISABLED state=tk.NORMAL if self.hotkey_code else tk.DISABLED
) )
@ -491,7 +487,7 @@ class PreferencesDialog(tk.Toplevel):
self.hotkey_play_btn = nb.Checkbutton( self.hotkey_play_btn = nb.Checkbutton(
config_frame, config_frame,
# LANG: Configuration - play sound when hotkey used # LANG: Configuration - play sound when hotkey used
text=_('Play sound'), text=tr.tl('Play sound'),
variable=self.hotkey_play, variable=self.hotkey_play,
state=tk.NORMAL if self.hotkey_code else tk.DISABLED state=tk.NORMAL if self.hotkey_code else tk.DISABLED
) )
@ -506,7 +502,7 @@ class PreferencesDialog(tk.Toplevel):
self.disable_autoappupdatecheckingame_btn = nb.Checkbutton( self.disable_autoappupdatecheckingame_btn = nb.Checkbutton(
config_frame, config_frame,
# LANG: Configuration - disable checks for app updates when in-game # 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, variable=self.disable_autoappupdatecheckingame,
command=self.disable_autoappupdatecheckingame_changed command=self.disable_autoappupdatecheckingame_changed
) )
@ -521,7 +517,7 @@ class PreferencesDialog(tk.Toplevel):
# Settings prompt for preferred ship loadout, system and station info websites # Settings prompt for preferred ship loadout, system and station info websites
# LANG: Label for preferred shipyard, system and station 'providers' # 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() 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 # Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis
# LANG: Label for Shipyard provider selection # 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( self.shipyard_button = nb.OptionMenu(
config_frame, self.shipyard_provider, self.shipyard_provider.get(), *plug.provides('shipyard_url') 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( self.alt_shipyard_open_btn = nb.Checkbutton(
config_frame, config_frame,
# LANG: Label for checkbox to utilise alternative Coriolis URL method # 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, variable=self.alt_shipyard_open,
command=self.alt_shipyard_open_changed, command=self.alt_shipyard_open_changed,
) )
@ -558,7 +556,7 @@ class PreferencesDialog(tk.Toplevel):
) )
# LANG: Configuration - Label for selection of 'System' provider website # 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( self.system_button = nb.OptionMenu(
config_frame, config_frame,
self.system_provider, self.system_provider,
@ -576,7 +574,7 @@ class PreferencesDialog(tk.Toplevel):
) )
# LANG: Configuration - Label for selection of 'Station' provider website # 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( self.station_button = nb.OptionMenu(
config_frame, config_frame,
self.station_provider, self.station_provider,
@ -597,7 +595,7 @@ class PreferencesDialog(tk.Toplevel):
nb.Label( nb.Label(
config_frame, config_frame,
# LANG: Configuration - Label for selection of Log Level # 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) ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
current_loglevel = config.get_str('loglevel') current_loglevel = config.get_str('loglevel')
@ -624,7 +622,7 @@ class PreferencesDialog(tk.Toplevel):
ttk.Button( ttk.Button(
config_frame, config_frame,
# LANG: Label on button used to open a filesystem folder # 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() command=lambda: help_open_log_folder()
).grid(column=2, padx=self.PADX, pady=0, sticky=tk.NSEW, row=cur_row) ).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()) nb.Label(config_frame).grid(sticky=tk.W, row=row.get())
# LANG: Label for 'Configuration' tab in Settings # 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: def __setup_privacy_tab(self, notebook: ttk.Notebook) -> None:
privacy_frame = nb.Frame(notebook) privacy_frame = nb.Frame(notebook)
@ -641,37 +639,37 @@ class PreferencesDialog(tk.Toplevel):
row = AutoInc(start=0) row = AutoInc(start=0)
# LANG: UI elements privacy section header in privacy tab of preferences # 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 row=row.get(), column=0, sticky=tk.W, padx=self.PADX, pady=self.PADY
) )
nb.Checkbutton( nb.Checkbutton(
# LANG: Hide private group owner name from UI checkbox # 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 variable=self.hide_private_group
).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W) ).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W)
nb.Checkbutton( nb.Checkbutton(
# LANG: Hide multicrew captain name from main UI checkbox # 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 variable=self.hide_multicrew_captain
).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W) ).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: def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None:
self.languages = Translations.available_names() self.languages = tr.available_names()
# Appearance theme and language setting # Appearance theme and language setting
# LANG: The system default language choice in Settings > Appearance # 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.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.minimize_system_tray = tk.BooleanVar(value=config.get_bool('minimize_system_tray'))
self.theme = tk.IntVar(value=config.get_int('theme')) self.theme = tk.IntVar(value=config.get_int('theme'))
self.theme_colors = [config.get_str('dark_text'), config.get_str('dark_highlight')] self.theme_colors = [config.get_str('dark_text'), config.get_str('dark_highlight')]
self.theme_prompts = [ self.theme_prompts = [
# LANG: Label for Settings > Appeareance > selection of 'normal' text colour # 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 # 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) row = AutoInc(start=0)
@ -680,7 +678,7 @@ class PreferencesDialog(tk.Toplevel):
appearance_frame.columnconfigure(2, weight=1) appearance_frame.columnconfigure(2, weight=1)
with row as cur_row: with row as cur_row:
# LANG: Appearance - Label for selection of application display language # 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 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()) 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 # Appearance setting
# LANG: Label for Settings > Appearance > Theme selection # 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() columnspan=3, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
) )
# Appearance theme and language setting # Appearance theme and language setting
nb.Radiobutton( nb.Radiobutton(
# LANG: Label for 'Default' theme radio button # 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 value=theme.THEME_DEFAULT, command=self.themevarchanged
).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
# Appearance theme setting # Appearance theme setting
nb.Radiobutton( nb.Radiobutton(
# LANG: Label for 'Dark' theme radio button # 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()) ).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
if sys.platform == 'win32': if sys.platform == 'win32':
nb.Radiobutton( nb.Radiobutton(
appearance_frame, appearance_frame,
# LANG: Label for 'Transparent' theme radio button # LANG: Label for 'Transparent' theme radio button
text=_('Transparent'), # Appearance theme setting text=tr.tl('Transparent'), # Appearance theme setting
variable=self.theme, variable=self.theme,
value=theme.THEME_TRANSPARENT, value=theme.THEME_TRANSPARENT,
command=self.themevarchanged command=self.themevarchanged
@ -727,7 +726,7 @@ class PreferencesDialog(tk.Toplevel):
self.theme_button_0 = tk.Button( self.theme_button_0 = tk.Button(
appearance_frame, appearance_frame,
# LANG: Appearance - Example 'Normal' text # LANG: Appearance - Example 'Normal' text
text=_('Station'), text=tr.tl('Station'),
background='grey4', background='grey4',
command=lambda: self.themecolorbrowse(0) command=lambda: self.themecolorbrowse(0)
) )
@ -759,7 +758,7 @@ class PreferencesDialog(tk.Toplevel):
) )
with row as cur_row: with row as cur_row:
# LANG: Appearance - Label for selection of UI scaling # 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 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( self.ui_scaling_defaultis = nb.Label(
appearance_frame, appearance_frame,
# LANG: Appearance - Help/hint text for UI scaling selection # 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) ).grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.E, row=cur_row)
# Transparency slider # Transparency slider
@ -790,7 +789,7 @@ class PreferencesDialog(tk.Toplevel):
with row as cur_row: with row as cur_row:
# LANG: Appearance - Label for selection of main window transparency # 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 padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row
) )
self.transparency = tk.IntVar() self.transparency = tk.IntVar()
@ -810,7 +809,7 @@ class PreferencesDialog(tk.Toplevel):
nb.Label( nb.Label(
appearance_frame, appearance_frame,
# LANG: Appearance - Help/hint text for Main window transparency selection # LANG: Appearance - Help/hint text for Main window transparency selection
text=_( text=tr.tl(
"100 means fully opaque.{CR}" "100 means fully opaque.{CR}"
"Window is updated in real time" "Window is updated in real time"
).format(CR='\n') ).format(CR='\n')
@ -832,7 +831,7 @@ class PreferencesDialog(tk.Toplevel):
self.ontop_button = nb.Checkbutton( self.ontop_button = nb.Checkbutton(
appearance_frame, appearance_frame,
# LANG: Appearance - Label for checkbox to select if application always on top # 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, variable=self.always_ontop,
command=self.themevarchanged command=self.themevarchanged
) )
@ -844,7 +843,7 @@ class PreferencesDialog(tk.Toplevel):
nb.Checkbutton( nb.Checkbutton(
appearance_frame, appearance_frame,
# LANG: Appearance option for Windows "minimize to system tray" # 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, variable=self.minimize_system_tray,
command=self.themevarchanged command=self.themevarchanged
).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance setting ).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 nb.Label(appearance_frame).grid(sticky=tk.W) # big spacer
# LANG: Label for Settings > Appearance tab # 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 def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001
# Plugin settings and info # Plugin settings and info
@ -864,7 +863,7 @@ class PreferencesDialog(tk.Toplevel):
# Section heading in settings # Section heading in settings
# LANG: Label for location of third-party plugins folder # 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() padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
) )
@ -877,13 +876,14 @@ class PreferencesDialog(tk.Toplevel):
plugins_frame, plugins_frame,
# Help text in settings # Help text in settings
# LANG: Tip/label about how to disable plugins # 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) ).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
ttk.Button( ttk.Button(
plugins_frame, plugins_frame,
# LANG: Label on button used to open a filesystem folder # 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}') 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) ).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( nb.Label(
plugins_frame, plugins_frame,
# LANG: Label on list of enabled plugins # 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()) ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
for plugin in enabled_plugins: 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() 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 # 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() padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
) )
HyperlinkLabel( HyperlinkLabel(
# LANG: Plugins - Label on URL to documentation about migrating plugins from Python 2.7 # 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'), background=nb.Label().cget('background'),
url='https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#migration-from-python-27', url='https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#migration-from-python-27',
underline=True underline=True
@ -943,7 +943,7 @@ class PreferencesDialog(tk.Toplevel):
nb.Label( nb.Label(
plugins_frame, plugins_frame,
# LANG: Label on list of user-disabled plugins # 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()) ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
for plugin in disabled_plugins: 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() 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 # 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() 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 # 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): def cmdrchanged(self, event=None):
""" """
@ -1122,7 +1122,7 @@ class PreferencesDialog(tk.Toplevel):
self.hotkey_text.insert( self.hotkey_text.insert(
0, 0,
# LANG: No hotkey/shortcut set # 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: def hotkeylisten(self, event: 'tk.Event[Any]') -> str:
""" """
@ -1155,7 +1155,7 @@ class PreferencesDialog(tk.Toplevel):
else: else:
# LANG: No hotkey/shortcut set # 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_only_btn['state'] = tk.DISABLED
self.hotkey_play_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 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 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 # Privacy options
config.set('hide_private_group', self.hide_private_group.get()) config.set('hide_private_group', self.hide_private_group.get())

235
stats.py
View File

@ -12,20 +12,17 @@ import json
import sys import sys
import tkinter as tk import tkinter as tk
from tkinter import ttk 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 companion
import EDMCLogging import EDMCLogging
import myNotebook as nb # noqa: N813 import myNotebook as nb # noqa: N813
from edmc_data import ship_name_map from edmc_data import ship_name_map
from hotkey import hotkeymgr from hotkey import hotkeymgr
from l10n import Locale from l10n import Locale, translations as tr
from monitor import monitor from monitor import monitor
logger = EDMCLogging.get_main_logger() logger = EDMCLogging.get_main_logger()
if TYPE_CHECKING:
def _(x: str) -> str: return x
if sys.platform == 'win32': if sys.platform == 'win32':
import ctypes import ctypes
from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT 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 # StatsResults assumes these three things are first
res = [ res = [
[_('Cmdr'), data['commander']['name']], # LANG: Cmdr stats [tr.tl('Cmdr'), data['commander']['name']], # LANG: Cmdr stats
[_('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats [tr.tl('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats
[_('Loan'), str(data['commander'].get('debt', 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_RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime
_('Elite'), # LANG: Top rank tr.tl('Elite'), # LANG: Top rank
_('Elite I'), # LANG: Top rank +1 tr.tl('Elite I'), # LANG: Top rank +1
_('Elite II'), # LANG: Top rank +2 tr.tl('Elite II'), # LANG: Top rank +2
_('Elite III'), # LANG: Top rank +3 tr.tl('Elite III'), # LANG: Top rank +3
_('Elite IV'), # LANG: Top rank +4 tr.tl('Elite IV'), # LANG: Top rank +4
_('Elite V'), # LANG: Top rank +5 tr.tl('Elite V'), # LANG: Top rank +5
] ]
RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime
# in output order # in output order
# Names we show people, vs internal names # Names we show people, vs internal names
(_('Combat'), 'combat'), # LANG: Ranking (tr.tl('Combat'), 'combat'), # LANG: Ranking
(_('Trade'), 'trade'), # LANG: Ranking (tr.tl('Trade'), 'trade'), # LANG: Ranking
(_('Explorer'), 'explore'), # LANG: Ranking (tr.tl('Explorer'), 'explore'), # LANG: Ranking
(_('Mercenary'), 'soldier'), # LANG: Ranking (tr.tl('Mercenary'), 'soldier'), # LANG: Ranking
(_('Exobiologist'), 'exobiologist'), # LANG: Ranking (tr.tl('Exobiologist'), 'exobiologist'), # LANG: Ranking
(_('CQC'), 'cqc'), # LANG: Ranking (tr.tl('CQC'), 'cqc'), # LANG: Ranking
(_('Federation'), 'federation'), # LANG: Ranking (tr.tl('Federation'), 'federation'), # LANG: Ranking
(_('Empire'), 'empire'), # LANG: Ranking (tr.tl('Empire'), 'empire'), # LANG: Ranking
(_('Powerplay'), 'power'), # LANG: Ranking (tr.tl('Powerplay'), 'power'), # LANG: Ranking
# ??? , 'crime'), # LANG: Ranking # ??? , 'crime'), # LANG: Ranking
# ??? , 'service'), # 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) # These names are the fdev side name (but lower()ed)
# http://elite-dangerous.wikia.com/wiki/Pilots_Federation#Ranks # http://elite-dangerous.wikia.com/wiki/Pilots_Federation#Ranks
'combat': [ 'combat': [
_('Harmless'), # LANG: Combat rank tr.tl('Harmless'), # LANG: Combat rank
_('Mostly Harmless'), # LANG: Combat rank tr.tl('Mostly Harmless'), # LANG: Combat rank
_('Novice'), # LANG: Combat rank tr.tl('Novice'), # LANG: Combat rank
_('Competent'), # LANG: Combat rank tr.tl('Competent'), # LANG: Combat rank
_('Expert'), # LANG: Combat rank tr.tl('Expert'), # LANG: Combat rank
_('Master'), # LANG: Combat rank tr.tl('Master'), # LANG: Combat rank
_('Dangerous'), # LANG: Combat rank tr.tl('Dangerous'), # LANG: Combat rank
_('Deadly'), # LANG: Combat rank tr.tl('Deadly'), # LANG: Combat rank
] + _ELITE_RANKS, ] + _ELITE_RANKS,
'trade': [ 'trade': [
_('Penniless'), # LANG: Trade rank tr.tl('Penniless'), # LANG: Trade rank
_('Mostly Penniless'), # LANG: Trade rank tr.tl('Mostly Penniless'), # LANG: Trade rank
_('Peddler'), # LANG: Trade rank tr.tl('Peddler'), # LANG: Trade rank
_('Dealer'), # LANG: Trade rank tr.tl('Dealer'), # LANG: Trade rank
_('Merchant'), # LANG: Trade rank tr.tl('Merchant'), # LANG: Trade rank
_('Broker'), # LANG: Trade rank tr.tl('Broker'), # LANG: Trade rank
_('Entrepreneur'), # LANG: Trade rank tr.tl('Entrepreneur'), # LANG: Trade rank
_('Tycoon'), # LANG: Trade rank tr.tl('Tycoon'), # LANG: Trade rank
] + _ELITE_RANKS, ] + _ELITE_RANKS,
'explore': [ 'explore': [
_('Aimless'), # LANG: Explorer rank tr.tl('Aimless'), # LANG: Explorer rank
_('Mostly Aimless'), # LANG: Explorer rank tr.tl('Mostly Aimless'), # LANG: Explorer rank
_('Scout'), # LANG: Explorer rank tr.tl('Scout'), # LANG: Explorer rank
_('Surveyor'), # LANG: Explorer rank tr.tl('Surveyor'), # LANG: Explorer rank
_('Trailblazer'), # LANG: Explorer rank tr.tl('Trailblazer'), # LANG: Explorer rank
_('Pathfinder'), # LANG: Explorer rank tr.tl('Pathfinder'), # LANG: Explorer rank
_('Ranger'), # LANG: Explorer rank tr.tl('Ranger'), # LANG: Explorer rank
_('Pioneer'), # LANG: Explorer rank tr.tl('Pioneer'), # LANG: Explorer rank
] + _ELITE_RANKS, ] + _ELITE_RANKS,
'soldier': [ 'soldier': [
_('Defenceless'), # LANG: Mercenary rank tr.tl('Defenceless'), # LANG: Mercenary rank
_('Mostly Defenceless'), # LANG: Mercenary rank tr.tl('Mostly Defenceless'), # LANG: Mercenary rank
_('Rookie'), # LANG: Mercenary rank tr.tl('Rookie'), # LANG: Mercenary rank
_('Soldier'), # LANG: Mercenary rank tr.tl('Soldier'), # LANG: Mercenary rank
_('Gunslinger'), # LANG: Mercenary rank tr.tl('Gunslinger'), # LANG: Mercenary rank
_('Warrior'), # LANG: Mercenary rank tr.tl('Warrior'), # LANG: Mercenary rank
_('Gunslinger'), # LANG: Mercenary rank tr.tl('Gunslinger'), # LANG: Mercenary rank
_('Deadeye'), # LANG: Mercenary rank tr.tl('Deadeye'), # LANG: Mercenary rank
] + _ELITE_RANKS, ] + _ELITE_RANKS,
'exobiologist': [ 'exobiologist': [
_('Directionless'), # LANG: Exobiologist rank tr.tl('Directionless'), # LANG: Exobiologist rank
_('Mostly Directionless'), # LANG: Exobiologist rank tr.tl('Mostly Directionless'), # LANG: Exobiologist rank
_('Compiler'), # LANG: Exobiologist rank tr.tl('Compiler'), # LANG: Exobiologist rank
_('Collector'), # LANG: Exobiologist rank tr.tl('Collector'), # LANG: Exobiologist rank
_('Cataloguer'), # LANG: Exobiologist rank tr.tl('Cataloguer'), # LANG: Exobiologist rank
_('Taxonomist'), # LANG: Exobiologist rank tr.tl('Taxonomist'), # LANG: Exobiologist rank
_('Ecologist'), # LANG: Exobiologist rank tr.tl('Ecologist'), # LANG: Exobiologist rank
_('Geneticist'), # LANG: Exobiologist rank tr.tl('Geneticist'), # LANG: Exobiologist rank
] + _ELITE_RANKS, ] + _ELITE_RANKS,
'cqc': [ 'cqc': [
_('Helpless'), # LANG: CQC rank tr.tl('Helpless'), # LANG: CQC rank
_('Mostly Helpless'), # LANG: CQC rank tr.tl('Mostly Helpless'), # LANG: CQC rank
_('Amateur'), # LANG: CQC rank tr.tl('Amateur'), # LANG: CQC rank
_('Semi Professional'), # LANG: CQC rank tr.tl('Semi Professional'), # LANG: CQC rank
_('Professional'), # LANG: CQC rank tr.tl('Professional'), # LANG: CQC rank
_('Champion'), # LANG: CQC rank tr.tl('Champion'), # LANG: CQC rank
_('Hero'), # LANG: CQC rank tr.tl('Hero'), # LANG: CQC rank
_('Gladiator'), # LANG: CQC rank tr.tl('Gladiator'), # LANG: CQC rank
] + _ELITE_RANKS, ] + _ELITE_RANKS,
# http://elite-dangerous.wikia.com/wiki/Federation#Ranks # http://elite-dangerous.wikia.com/wiki/Federation#Ranks
'federation': [ 'federation': [
_('None'), # LANG: No rank tr.tl('None'), # LANG: No rank
_('Recruit'), # LANG: Federation rank tr.tl('Recruit'), # LANG: Federation rank
_('Cadet'), # LANG: Federation rank tr.tl('Cadet'), # LANG: Federation rank
_('Midshipman'), # LANG: Federation rank tr.tl('Midshipman'), # LANG: Federation rank
_('Petty Officer'), # LANG: Federation rank tr.tl('Petty Officer'), # LANG: Federation rank
_('Chief Petty Officer'), # LANG: Federation rank tr.tl('Chief Petty Officer'), # LANG: Federation rank
_('Warrant Officer'), # LANG: Federation rank tr.tl('Warrant Officer'), # LANG: Federation rank
_('Ensign'), # LANG: Federation rank tr.tl('Ensign'), # LANG: Federation rank
_('Lieutenant'), # LANG: Federation rank tr.tl('Lieutenant'), # LANG: Federation rank
_('Lieutenant Commander'), # LANG: Federation rank tr.tl('Lieutenant Commander'), # LANG: Federation rank
_('Post Commander'), # LANG: Federation rank tr.tl('Post Commander'), # LANG: Federation rank
_('Post Captain'), # LANG: Federation rank tr.tl('Post Captain'), # LANG: Federation rank
_('Rear Admiral'), # LANG: Federation rank tr.tl('Rear Admiral'), # LANG: Federation rank
_('Vice Admiral'), # LANG: Federation rank tr.tl('Vice Admiral'), # LANG: Federation rank
_('Admiral') # LANG: Federation rank tr.tl('Admiral') # LANG: Federation rank
], ],
# http://elite-dangerous.wikia.com/wiki/Empire#Ranks # http://elite-dangerous.wikia.com/wiki/Empire#Ranks
'empire': [ 'empire': [
_('None'), # LANG: No rank tr.tl('None'), # LANG: No rank
_('Outsider'), # LANG: Empire rank tr.tl('Outsider'), # LANG: Empire rank
_('Serf'), # LANG: Empire rank tr.tl('Serf'), # LANG: Empire rank
_('Master'), # LANG: Empire rank tr.tl('Master'), # LANG: Empire rank
_('Squire'), # LANG: Empire rank tr.tl('Squire'), # LANG: Empire rank
_('Knight'), # LANG: Empire rank tr.tl('Knight'), # LANG: Empire rank
_('Lord'), # LANG: Empire rank tr.tl('Lord'), # LANG: Empire rank
_('Baron'), # LANG: Empire rank tr.tl('Baron'), # LANG: Empire rank
_('Viscount'), # LANG: Empire rank tr.tl('Viscount'), # LANG: Empire rank
_('Count'), # LANG: Empire rank tr.tl('Count'), # LANG: Empire rank
_('Earl'), # LANG: Empire rank tr.tl('Earl'), # LANG: Empire rank
_('Marquis'), # LANG: Empire rank tr.tl('Marquis'), # LANG: Empire rank
_('Duke'), # LANG: Empire rank tr.tl('Duke'), # LANG: Empire rank
_('Prince'), # LANG: Empire rank tr.tl('Prince'), # LANG: Empire rank
_('King') # LANG: Empire rank tr.tl('King') # LANG: Empire rank
], ],
# http://elite-dangerous.wikia.com/wiki/Ratings # http://elite-dangerous.wikia.com/wiki/Ratings
'power': [ 'power': [
_('None'), # LANG: No rank tr.tl('None'), # LANG: No rank
_('Rating 1'), # LANG: Power rank tr.tl('Rating 1'), # LANG: Power rank
_('Rating 2'), # LANG: Power rank tr.tl('Rating 2'), # LANG: Power rank
_('Rating 3'), # LANG: Power rank tr.tl('Rating 3'), # LANG: Power rank
_('Rating 4'), # LANG: Power rank tr.tl('Rating 4'), # LANG: Power rank
_('Rating 5') # 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}']) res.append([title, names[rank] if rank < len(names) else f'Rank {rank}'])
else: else:
res.append([title, _('None')]) # LANG: No rank res.append([title, tr.tl('None')]) # LANG: No rank
return res return res
@ -318,7 +315,7 @@ class StatsDialog():
if not monitor.cmdr: if not monitor.cmdr:
hotkeymgr.play_bad() hotkeymgr.play_bad()
# LANG: Current commander unknown when trying to use 'File' > 'Status' # 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 return
# TODO: This needs to use cached data # TODO: This needs to use cached data
@ -326,7 +323,7 @@ class StatsDialog():
logger.info('No cached data, aborting...') logger.info('No cached data, aborting...')
hotkeymgr.play_bad() hotkeymgr.play_bad()
# LANG: No Frontier CAPI data yet when trying to use 'File' > 'Status' # 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 return
capi_data = json.loads( capi_data = json.loads(
@ -336,7 +333,7 @@ class StatsDialog():
if not capi_data.get('commander') or not capi_data['commander'].get('name', '').strip(): if not capi_data.get('commander') or not capi_data['commander'].get('name', '').strip():
# Shouldn't happen # Shouldn't happen
# LANG: Unknown commander # LANG: Unknown commander
self.status['text'] = _("Who are you?!") self.status['text'] = tr.tl("Who are you?!")
elif ( elif (
not capi_data.get('lastSystem') not capi_data.get('lastSystem')
@ -344,7 +341,7 @@ class StatsDialog():
): ):
# Shouldn't happen # Shouldn't happen
# LANG: Unknown location # LANG: Unknown location
self.status['text'] = _("Where are you?!") self.status['text'] = tr.tl("Where are you?!")
elif ( elif (
not capi_data.get('ship') or not capi_data['ship'].get('modules') not capi_data.get('ship') or not capi_data['ship'].get('modules')
@ -352,7 +349,7 @@ class StatsDialog():
): ):
# Shouldn't happen # Shouldn't happen
# LANG: Unknown ship # LANG: Unknown ship
self.status['text'] = _("What are you flying?!") self.status['text'] = tr.tl("What are you flying?!")
else: else:
self.status['text'] = '' self.status['text'] = ''
@ -401,14 +398,14 @@ class StatsResults(tk.Toplevel):
self.addpagerow(page, thing, with_copy=True) self.addpagerow(page, thing, with_copy=True)
ttk.Frame(page).grid(pady=5) # bottom spacer 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, [ page = self.addpage(notebook, [
_('Ship'), # LANG: Status dialog subtitle tr.tl('Ship'), # LANG: Status dialog subtitle
'', '',
_('System'), # LANG: Main window tr.tl('System'), # LANG: Main window
_('Station'), # LANG: Status dialog subtitle tr.tl('Station'), # LANG: Status dialog subtitle
_('Value'), # LANG: Status dialog subtitle - CR value of ship tr.tl('Value'), # LANG: Status dialog subtitle - CR value of ship
]) ])
shiplist = ships(data) 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) 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 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 # wait for window to appear on screen before calling grab_set
self.wait_visibility() self.wait_visibility()

View File

@ -16,17 +16,14 @@ import tkinter as tk
from os.path import join from os.path import join
from tkinter import font as tk_font from tkinter import font as tk_font
from tkinter import ttk 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 config import config
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from ttkHyperlinkLabel import HyperlinkLabel from ttkHyperlinkLabel import HyperlinkLabel
logger = get_main_logger() logger = get_main_logger()
if TYPE_CHECKING:
def _(x: str) -> str: ...
if __debug__: if __debug__:
from traceback import print_exc from traceback import print_exc
@ -291,7 +288,7 @@ class _Theme:
# Font only supports Latin 1 / Supplement / Extended, and a # Font only supports Latin 1 / Supplement / Extended, and a
# few General Punctuation and Mathematical Operators # few General Punctuation and Mathematical Operators
# LANG: Label for commander name in main window # 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 tk_font.Font(family='Euro Caps', size=10, weight=tk_font.NORMAL) or
'TkDefaultFont'), 'TkDefaultFont'),
} }

View File

@ -25,10 +25,8 @@ import tkinter as tk
import webbrowser import webbrowser
from tkinter import font as tk_font from tkinter import font as tk_font
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Any from typing import Any
from l10n import translations as tr
if TYPE_CHECKING:
def _(x: str) -> str: return x
class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore 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) self.menu = tk.Menu(tearoff=tk.FALSE)
# LANG: Label for 'Copy' as in 'Copy and Paste' # 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('<Button-3>', self._contextmenu) self.bind('<Button-3>', self._contextmenu)
self.bind('<Enter>', self._enter) self.bind('<Enter>', self._enter)

View File

@ -16,10 +16,10 @@ import requests
import semantic_version import semantic_version
from config import appname, appversion_nobuild, config, update_feed from config import appname, appversion_nobuild, config, update_feed
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from l10n import translations as tr
if TYPE_CHECKING: if TYPE_CHECKING:
import tkinter as tk import tkinter as tk
def _(x: str): return x
logger = get_main_logger() logger = get_main_logger()
@ -200,7 +200,7 @@ class Updater:
if newversion and self.root: if newversion and self.root:
status = self.root.nametowidget(f'.{appname.lower()}.status') status = self.root.nametowidget(f'.{appname.lower()}.status')
# LANG: Update Available Text # 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() self.root.update_idletasks()
else: else: