mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-12 15:27:14 +03:00
Merge branch 'develop' into fix/2166/update-watchdog-typehints
This commit is contained in:
commit
99a23d40cd
13
.github/SECURITY.md
vendored
Normal file
13
.github/SECURITY.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# Reporting Security Issues
|
||||
|
||||
EDMC takes security very seriously. Our users trust us to provide a secure and safe tool to support their experience in Elite.
|
||||
|
||||
In general, the best way to report a major security issue with us that should not be publically discussed is to email our maintainer teams.
|
||||
|
||||
The best point of contact for this is edmc@hullseals.space. When contacting, be sure to include as much information in your report.
|
||||
|
||||
As soon as your report is processed, we'll get in touch to make sure we quickly move ahead with fixing the issue and will lay out a timeline for public disclosure and fixes.
|
||||
|
||||
Another method of reporting vulnerabilities is to open a new Bug Report [here](https://github.com/EDCD/EDMarketConnector/issues/new?assignees=&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title=).
|
||||
|
||||
If reporting a security issue here, do not include details as to the issue or steps to reproduce, simply indicate you have found a potential security bug and would like us to contact you directly.
|
18
.github/pull_request_template.md
vendored
Normal file
18
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<!---
|
||||
Thank you for submitting a PR for EDMC! Please follow this template to ensure your PR is processed.
|
||||
In general, you should be submitting targeting the develop branch. Please make sure you have this selected.
|
||||
-->
|
||||
# Description
|
||||
<!-- What does this PR Do? -->
|
||||
|
||||
# Example Images
|
||||
<!-- Only if relevant. Remove if irrelevant. -->
|
||||
|
||||
# Type of Change
|
||||
<!-- What type of change is this? New Feature? Enhancement to Existing Feature? Bug Fix? Translation Update? -->
|
||||
|
||||
# How Tested
|
||||
<!-- How have you tested this change to ensure it works and doesn't cause new issues -->
|
||||
|
||||
# Notes
|
||||
<!-- Does this resolve any open issues? Was this PR discussed internally somewhere off GitHub? -->
|
95
.github/workflows/codeql.yml
vendored
Normal file
95
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'stable'
|
||||
- 'releases'
|
||||
- 'beta'
|
||||
pull_request:
|
||||
branches: [ develop ]
|
||||
schedule:
|
||||
- cron: '38 5 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 360
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: python
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
47
ChangeLog.md
47
ChangeLog.md
@ -6,6 +6,53 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are
|
||||
in the source (not distributed with the Windows installer) for the
|
||||
currently used version.
|
||||
---
|
||||
Release 5.10.6
|
||||
===
|
||||
This release contains the data information for the new SCO modules added in Elite update 18.04.
|
||||
This should represent full support for the new Python Mk II.
|
||||
|
||||
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
|
||||
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
|
||||
|
||||
**Changes and Enhancements**
|
||||
* Added new SCO Module Details
|
||||
* Reverted a change from the prior release due to breaking some consumers.
|
||||
**Plugin Developers**
|
||||
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
|
||||
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
|
||||
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
|
||||
|
||||
**Plugin Developers**
|
||||
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
|
||||
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
|
||||
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
|
||||
|
||||
Release 5.10.5
|
||||
===
|
||||
This release contains a fix for a bug that could crash EDMC's console versions when reading outfitting information
|
||||
from the new SCO Frame Shift Drive modules.
|
||||
|
||||
Please note that this does not offer full support for the new SCO modules or the Python Mk II. More support will
|
||||
be added in a future update.
|
||||
|
||||
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
|
||||
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
|
||||
|
||||
**Changes and Enhancements**
|
||||
* Updated Translations
|
||||
* Added limited data regarding the Python Mk II
|
||||
* Added a few Coriolis module information entries
|
||||
|
||||
**Bug Fixes**
|
||||
* Fixed a bug that could cause the new SCO modules to display improper ratings or sizes
|
||||
* Fixed a bug where the new SCO modules would display as a normal Frame Shift Drive
|
||||
* Fixed a bug which could crash EDMC if the exact details of a Frame Shift Drive were unknown
|
||||
|
||||
**Plugin Developers**
|
||||
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
|
||||
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
|
||||
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
|
||||
|
||||
Release 5.10.4
|
||||
===
|
||||
This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also
|
||||
|
7
EDMC.py
7
EDMC.py
@ -26,7 +26,6 @@ from EDMCLogging import edmclogger, logger, logging
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy
|
||||
def _(x: str): return x
|
||||
|
||||
edmclogger.set_channels_loglevel(logging.INFO)
|
||||
|
||||
@ -35,7 +34,7 @@ import collate
|
||||
import commodity
|
||||
import companion
|
||||
import edshipyard
|
||||
import l10n
|
||||
from l10n import translations as tr
|
||||
import loadout
|
||||
import outfitting
|
||||
import shipyard
|
||||
@ -66,7 +65,7 @@ Locale LC_TIME: {locale.getlocale(locale.LC_TIME)}'''
|
||||
)
|
||||
|
||||
|
||||
l10n.Translations.install_dummy()
|
||||
tr.install_dummy()
|
||||
|
||||
SERVER_RETRY = 5 # retry pause for Companion servers [s]
|
||||
EXIT_SUCCESS, EXIT_SERVER, EXIT_CREDENTIALS, EXIT_VERIFICATION, EXIT_LAGGING, EXIT_SYS_ERR, EXIT_ARGS, \
|
||||
@ -164,7 +163,7 @@ def main(): # noqa: C901, CCR001
|
||||
newversion: EDMCVersion | None = updater.check_appcast()
|
||||
if newversion:
|
||||
# LANG: Update Available Text
|
||||
newverstr: str = _("{NEWVER} is available").format(NEWVER=newversion.title)
|
||||
newverstr: str = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title)
|
||||
print(f'{appversion()} ({newverstr})')
|
||||
else:
|
||||
print(appversion())
|
||||
|
@ -18,6 +18,7 @@ import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import webbrowser
|
||||
import tempfile
|
||||
from os import chdir, environ, path
|
||||
from time import localtime, strftime, time
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
@ -47,8 +48,6 @@ if __name__ == '__main__':
|
||||
# output until after this redirect is done, if needed.
|
||||
if getattr(sys, 'frozen', False):
|
||||
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
|
||||
import tempfile
|
||||
|
||||
# unbuffered not allowed for text in python3, so use `1 for line buffering
|
||||
log_file_path = path.join(tempfile.gettempdir(), f'{appname}.log')
|
||||
sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here.
|
||||
@ -413,9 +412,6 @@ if TYPE_CHECKING:
|
||||
from infi.systray import SysTrayIcon
|
||||
# isort: on
|
||||
|
||||
def _(x: str) -> str:
|
||||
"""Fake the l10n translation functions for typing."""
|
||||
return x
|
||||
|
||||
import tkinter as tk
|
||||
import tkinter.filedialog
|
||||
@ -432,7 +428,7 @@ from commodity import COMMODITY_CSV
|
||||
from dashboard import dashboard
|
||||
from edmc_data import ship_name_map
|
||||
from hotkey import hotkeymgr
|
||||
from l10n import Translations
|
||||
from l10n import translations as tr
|
||||
from monitor import monitor
|
||||
from theme import theme
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
@ -590,7 +586,7 @@ class AppWindow:
|
||||
self.button = ttk.Button(
|
||||
frame,
|
||||
name='update_button',
|
||||
text=_('Update'), # LANG: Main UI Update button
|
||||
text=tr.tl('Update'), # LANG: Main UI Update button
|
||||
width=28,
|
||||
default=tk.ACTIVE,
|
||||
state=tk.DISABLED
|
||||
@ -651,7 +647,8 @@ class AppWindow:
|
||||
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
||||
# About E:D Market Connector
|
||||
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
|
||||
self.help_menu.add_command(command=prefs.help_open_log_folder) # Open Log Folder
|
||||
logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
|
||||
self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder
|
||||
|
||||
self.menubar.add_cascade(menu=self.help_menu)
|
||||
if sys.platform == 'win32':
|
||||
@ -660,7 +657,7 @@ class AppWindow:
|
||||
self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
|
||||
self.system_menu.add_separator()
|
||||
# LANG: Appearance - Label for checkbox to select if application always on top
|
||||
self.system_menu.add_checkbutton(label=_('Always on top'),
|
||||
self.system_menu.add_checkbutton(label=tr.tl('Always on top'),
|
||||
variable=self.always_ontop,
|
||||
command=self.ontop_changed) # Appearance setting
|
||||
self.menubar.add_cascade(menu=self.system_menu)
|
||||
@ -765,7 +762,7 @@ class AppWindow:
|
||||
# Check for Valid Providers
|
||||
validate_providers()
|
||||
if monitor.cmdr is None:
|
||||
self.status['text'] = _("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game
|
||||
self.status['text'] = tr.tl("Awaiting Full CMDR Login") # LANG: Await Full CMDR Login to Game
|
||||
|
||||
# Start a protocol handler to handle cAPI registration. Requires main loop to be running.
|
||||
self.w.after_idle(lambda: protocol.protocolhandler.start(self.w))
|
||||
@ -795,7 +792,7 @@ class AppWindow:
|
||||
|
||||
suit = monitor.state.get('SuitCurrent')
|
||||
if suit is None:
|
||||
self.suit['text'] = f'<{_("Unknown")}>' # LANG: Unknown suit
|
||||
self.suit['text'] = f'<{tr.tl("Unknown")}>' # LANG: Unknown suit
|
||||
return
|
||||
|
||||
suitname = suit['edmcName']
|
||||
@ -851,45 +848,45 @@ class AppWindow:
|
||||
# (Re-)install log monitoring
|
||||
if not monitor.start(self.w):
|
||||
# LANG: ED Journal file location appears to be in error
|
||||
self.status['text'] = _('Error: Check E:D journal file location')
|
||||
self.status['text'] = tr.tl('Error: Check E:D journal file location')
|
||||
|
||||
if dologin and monitor.cmdr:
|
||||
self.login() # Login if not already logged in with this Cmdr
|
||||
|
||||
def set_labels(self):
|
||||
"""Set main window labels, e.g. after language change."""
|
||||
self.cmdr_label['text'] = _('Cmdr') + ':' # LANG: Label for commander name in main window
|
||||
self.cmdr_label['text'] = tr.tl('Cmdr') + ':' # LANG: Label for commander name in main window
|
||||
# LANG: 'Ship' or multi-crew role label in main window, as applicable
|
||||
self.ship_label['text'] = (monitor.state['Captain'] and _('Role') or _('Ship')) + ':' # Main window
|
||||
self.suit_label['text'] = _('Suit') + ':' # LANG: Label for 'Suit' line in main UI
|
||||
self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI
|
||||
self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI
|
||||
self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window
|
||||
self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title
|
||||
self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title
|
||||
self.menubar.entryconfigure(3, label=_('Help')) # LANG: 'Help' menu title
|
||||
self.theme_file_menu['text'] = _('File') # LANG: 'File' menu title
|
||||
self.theme_edit_menu['text'] = _('Edit') # LANG: 'Edit' menu title
|
||||
self.theme_help_menu['text'] = _('Help') # LANG: 'Help' menu title
|
||||
self.ship_label['text'] = (monitor.state['Captain'] and tr.tl('Role') or tr.tl('Ship')) + ':' # Main window
|
||||
self.suit_label['text'] = tr.tl('Suit') + ':' # LANG: Label for 'Suit' line in main UI
|
||||
self.system_label['text'] = tr.tl('System') + ':' # LANG: Label for 'System' line in main UI
|
||||
self.station_label['text'] = tr.tl('Station') + ':' # LANG: Label for 'Station' line in main UI
|
||||
self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window
|
||||
self.menubar.entryconfigure(1, label=tr.tl('File')) # LANG: 'File' menu title
|
||||
self.menubar.entryconfigure(2, label=tr.tl('Edit')) # LANG: 'Edit' menu title
|
||||
self.menubar.entryconfigure(3, label=tr.tl('Help')) # LANG: 'Help' menu title
|
||||
self.theme_file_menu['text'] = tr.tl('File') # LANG: 'File' menu title
|
||||
self.theme_edit_menu['text'] = tr.tl('Edit') # LANG: 'Edit' menu title
|
||||
self.theme_help_menu['text'] = tr.tl('Help') # LANG: 'Help' menu title
|
||||
|
||||
# File menu
|
||||
self.file_menu.entryconfigure(0, label=_('Status')) # LANG: File > Status
|
||||
self.file_menu.entryconfigure(1, label=_('Save Raw Data...')) # LANG: File > Save Raw Data...
|
||||
self.file_menu.entryconfigure(2, label=_('Settings')) # LANG: File > Settings
|
||||
self.file_menu.entryconfigure(4, label=_('Exit')) # LANG: File > Exit
|
||||
self.file_menu.entryconfigure(0, label=tr.tl('Status')) # LANG: File > Status
|
||||
self.file_menu.entryconfigure(1, label=tr.tl('Save Raw Data...')) # LANG: File > Save Raw Data...
|
||||
self.file_menu.entryconfigure(2, label=tr.tl('Settings')) # LANG: File > Settings
|
||||
self.file_menu.entryconfigure(4, label=tr.tl('Exit')) # LANG: File > Exit
|
||||
|
||||
# Help menu
|
||||
self.help_menu.entryconfigure(0, label=_('Documentation')) # LANG: Help > Documentation
|
||||
self.help_menu.entryconfigure(1, label=_('Troubleshooting')) # LANG: Help > Troubleshooting
|
||||
self.help_menu.entryconfigure(2, label=_('Report A Bug')) # LANG: Help > Report A Bug
|
||||
self.help_menu.entryconfigure(3, label=_('Privacy Policy')) # LANG: Help > Privacy Policy
|
||||
self.help_menu.entryconfigure(4, label=_('Release Notes')) # LANG: Help > Release Notes
|
||||
self.help_menu.entryconfigure(5, label=_('Check for Updates...')) # LANG: Help > Check for Updates...
|
||||
self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname)) # LANG: Help > About App
|
||||
self.help_menu.entryconfigure(7, label=_('Open Log Folder')) # LANG: Help > Open Log Folder
|
||||
self.help_menu.entryconfigure(0, label=tr.tl('Documentation')) # LANG: Help > Documentation
|
||||
self.help_menu.entryconfigure(1, label=tr.tl('Troubleshooting')) # LANG: Help > Troubleshooting
|
||||
self.help_menu.entryconfigure(2, label=tr.tl('Report A Bug')) # LANG: Help > Report A Bug
|
||||
self.help_menu.entryconfigure(3, label=tr.tl('Privacy Policy')) # LANG: Help > Privacy Policy
|
||||
self.help_menu.entryconfigure(4, label=tr.tl('Release Notes')) # LANG: Help > Release Notes
|
||||
self.help_menu.entryconfigure(5, label=tr.tl('Check for Updates...')) # LANG: Help > Check for Updates...
|
||||
self.help_menu.entryconfigure(6, label=tr.tl("About {APP}").format(APP=applongname)) # LANG: Help > About App
|
||||
self.help_menu.entryconfigure(7, label=tr.tl('Open Log Folder')) # LANG: Help > Open Log Folder
|
||||
|
||||
# Edit menu
|
||||
self.edit_menu.entryconfigure(0, label=_('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||
self.edit_menu.entryconfigure(0, label=tr.tl('Copy')) # LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||
|
||||
def login(self):
|
||||
"""Initiate CAPI/Frontier login and set other necessary state."""
|
||||
@ -900,12 +897,12 @@ class AppWindow:
|
||||
if should_return:
|
||||
logger.warning('capi.auth has been disabled via killswitch. Returning.')
|
||||
# LANG: CAPI auth aborted because of killswitch
|
||||
self.status['text'] = _('CAPI auth disabled by killswitch')
|
||||
self.status['text'] = tr.tl('CAPI auth disabled by killswitch')
|
||||
return
|
||||
|
||||
if not self.status['text']:
|
||||
# LANG: Status - Attempting to get a Frontier Auth Access Token
|
||||
self.status['text'] = _('Logging in...')
|
||||
self.status['text'] = tr.tl('Logging in...')
|
||||
|
||||
self.button['state'] = self.theme_button['state'] = tk.DISABLED
|
||||
|
||||
@ -916,7 +913,7 @@ class AppWindow:
|
||||
try:
|
||||
if companion.session.login(monitor.cmdr, monitor.is_beta):
|
||||
# LANG: Successfully authenticated with the Frontier website
|
||||
self.status['text'] = _('Authentication successful')
|
||||
self.status['text'] = tr.tl('Authentication successful')
|
||||
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||
@ -947,17 +944,17 @@ class AppWindow:
|
||||
# Signal as error because the user might actually be docked
|
||||
# but the server hosting the Companion API hasn't caught up
|
||||
# LANG: Player is not docked at a station, when we expect them to be
|
||||
self._handle_status(_("You're not docked at a station!"))
|
||||
self._handle_status(tr.tl("You're not docked at a station!"))
|
||||
return False
|
||||
|
||||
# Ignore possibly missing shipyard info
|
||||
if output_flags & config.OUT_EDDN_SEND_STATION_DATA and not (has_commodities or has_modules):
|
||||
# LANG: Status - Either no market or no modules data for station from Frontier CAPI
|
||||
self._handle_status(_("Station doesn't have anything!"))
|
||||
self._handle_status(tr.tl("Station doesn't have anything!"))
|
||||
|
||||
elif not has_commodities:
|
||||
# LANG: Status - No station market data from Frontier CAPI
|
||||
self._handle_status(_("Station doesn't have a market!"))
|
||||
self._handle_status(tr.tl("Station doesn't have a market!"))
|
||||
|
||||
elif output_flags & commodities_flag:
|
||||
# Fixup anomalies in the comodity data
|
||||
@ -995,7 +992,7 @@ class AppWindow:
|
||||
if should_return:
|
||||
logger.warning('capi.auth has been disabled via killswitch. Returning.')
|
||||
# LANG: CAPI auth query aborted because of killswitch
|
||||
self.status['text'] = _('CAPI auth disabled by killswitch')
|
||||
self.status['text'] = tr.tl('CAPI auth disabled by killswitch')
|
||||
hotkeymgr.play_bad()
|
||||
return
|
||||
|
||||
@ -1005,37 +1002,37 @@ class AppWindow:
|
||||
if not monitor.cmdr:
|
||||
logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown')
|
||||
# LANG: CAPI queries aborted because Cmdr name is unknown
|
||||
self.status['text'] = _('CAPI query aborted: Cmdr name unknown')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: Cmdr name unknown')
|
||||
return
|
||||
|
||||
if not monitor.mode:
|
||||
logger.trace_if('capi.worker', 'Aborting Query: Game Mode unknown')
|
||||
# LANG: CAPI queries aborted because game mode unknown
|
||||
self.status['text'] = _('CAPI query aborted: Game mode unknown')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: Game mode unknown')
|
||||
return
|
||||
|
||||
if monitor.state['GameVersion'] is None:
|
||||
logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown')
|
||||
# LANG: CAPI queries aborted because GameVersion unknown
|
||||
self.status['text'] = _('CAPI query aborted: GameVersion unknown')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: GameVersion unknown')
|
||||
return
|
||||
|
||||
if not monitor.state['SystemName']:
|
||||
logger.trace_if('capi.worker', 'Aborting Query: Current star system unknown')
|
||||
# LANG: CAPI queries aborted because current star system name unknown
|
||||
self.status['text'] = _('CAPI query aborted: Current system unknown')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: Current system unknown')
|
||||
return
|
||||
|
||||
if monitor.state['Captain']:
|
||||
logger.trace_if('capi.worker', 'Aborting Query: In multi-crew')
|
||||
# LANG: CAPI queries aborted because player is in multi-crew on other Cmdr's ship
|
||||
self.status['text'] = _('CAPI query aborted: In other-ship multi-crew')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: In other-ship multi-crew')
|
||||
return
|
||||
|
||||
if monitor.mode == 'CQC':
|
||||
logger.trace_if('capi.worker', 'Aborting Query: In CQC')
|
||||
# LANG: CAPI queries aborted because player is in CQC (Arena)
|
||||
self.status['text'] = _('CAPI query aborted: CQC (Arena) detected')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: CQC (Arena) detected')
|
||||
return
|
||||
|
||||
if companion.session.state == companion.Session.STATE_AUTH:
|
||||
@ -1056,7 +1053,7 @@ class AppWindow:
|
||||
hotkeymgr.play_good()
|
||||
|
||||
# LANG: Status - Attempting to retrieve data from Frontier CAPI
|
||||
self.status['text'] = _('Fetching data...')
|
||||
self.status['text'] = tr.tl('Fetching data...')
|
||||
self.button['state'] = self.theme_button['state'] = tk.DISABLED
|
||||
self.w.update_idletasks()
|
||||
|
||||
@ -1086,20 +1083,20 @@ class AppWindow:
|
||||
if should_return:
|
||||
logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.')
|
||||
# LANG: CAPI fleetcarrier query aborted because of killswitch
|
||||
self.status['text'] = _('CAPI fleetcarrier disabled by killswitch')
|
||||
self.status['text'] = tr.tl('CAPI fleetcarrier disabled by killswitch')
|
||||
hotkeymgr.play_bad()
|
||||
return
|
||||
|
||||
if not monitor.cmdr:
|
||||
logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown')
|
||||
# LANG: CAPI fleetcarrier query aborted because Cmdr name is unknown
|
||||
self.status['text'] = _('CAPI query aborted: Cmdr name unknown')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: Cmdr name unknown')
|
||||
return
|
||||
|
||||
if monitor.state['GameVersion'] is None:
|
||||
logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown')
|
||||
# LANG: CAPI fleetcarrier query aborted because GameVersion unknown
|
||||
self.status['text'] = _('CAPI query aborted: GameVersion unknown')
|
||||
self.status['text'] = tr.tl('CAPI query aborted: GameVersion unknown')
|
||||
return
|
||||
|
||||
if not companion.session.retrying:
|
||||
@ -1108,7 +1105,7 @@ class AppWindow:
|
||||
return
|
||||
|
||||
# LANG: Status - Attempting to retrieve data from Frontier CAPI
|
||||
self.status['text'] = _('Fetching data...')
|
||||
self.status['text'] = tr.tl('Fetching data...')
|
||||
self.w.update_idletasks()
|
||||
|
||||
query_time = int(time())
|
||||
@ -1151,11 +1148,11 @@ class AppWindow:
|
||||
# Validation
|
||||
if 'name' not in capi_response.capi_data:
|
||||
# LANG: No data was returned for the fleetcarrier from the Frontier CAPI
|
||||
err = self.status['text'] = _('CAPI: No fleetcarrier data returned')
|
||||
err = self.status['text'] = tr.tl('CAPI: No fleetcarrier data returned')
|
||||
|
||||
elif not capi_response.capi_data.get('name', {}).get('callsign'):
|
||||
# LANG: We didn't have the fleetcarrier callsign when we should have
|
||||
err = self.status['text'] = _("CAPI: Fleetcarrier data incomplete") # Shouldn't happen
|
||||
err = self.status['text'] = tr.tl("CAPI: Fleetcarrier data incomplete") # Shouldn't happen
|
||||
|
||||
else:
|
||||
if __debug__: # Recording
|
||||
@ -1174,24 +1171,24 @@ class AppWindow:
|
||||
elif 'commander' not in capi_response.capi_data:
|
||||
# This can happen with EGS Auth if no commander created yet
|
||||
# LANG: No data was returned for the commander from the Frontier CAPI
|
||||
err = self.status['text'] = _('CAPI: No commander data returned')
|
||||
err = self.status['text'] = tr.tl('CAPI: No commander data returned')
|
||||
|
||||
elif not capi_response.capi_data.get('commander', {}).get('name'):
|
||||
# LANG: We didn't have the commander name when we should have
|
||||
err = self.status['text'] = _("Who are you?!") # Shouldn't happen
|
||||
err = self.status['text'] = tr.tl("Who are you?!") # Shouldn't happen
|
||||
|
||||
elif (not capi_response.capi_data.get('lastSystem', {}).get('name')
|
||||
or (capi_response.capi_data['commander'].get('docked')
|
||||
and not capi_response.capi_data.get('lastStarport', {}).get('name'))):
|
||||
# LANG: We don't know where the commander is, when we should
|
||||
err = self.status['text'] = _("Where are you?!") # Shouldn't happen
|
||||
err = self.status['text'] = tr.tl("Where are you?!") # Shouldn't happen
|
||||
|
||||
elif (
|
||||
not capi_response.capi_data.get('ship', {}).get('name')
|
||||
or not capi_response.capi_data.get('ship', {}).get('modules')
|
||||
):
|
||||
# LANG: We don't know what ship the commander is in, when we should
|
||||
err = self.status['text'] = _("What are you flying?!") # Shouldn't happen
|
||||
err = self.status['text'] = tr.tl("What are you flying?!") # Shouldn't happen
|
||||
|
||||
elif monitor.cmdr and capi_response.capi_data['commander']['name'] != monitor.cmdr:
|
||||
# Companion API Commander doesn't match Journal
|
||||
@ -1318,7 +1315,7 @@ class AppWindow:
|
||||
|
||||
except companion.ServerConnectionError as comp_err:
|
||||
# LANG: Frontier CAPI server error when fetching data
|
||||
self.status['text'] = _('Frontier CAPI server error')
|
||||
self.status['text'] = tr.tl('Frontier CAPI server error')
|
||||
logger.warning(f'Exception while contacting server: {comp_err}')
|
||||
err = self.status['text'] = str(comp_err)
|
||||
play_bad = True
|
||||
@ -1327,7 +1324,7 @@ class AppWindow:
|
||||
# We need to 'close' the auth else it'll see STATE_OK and think login() isn't needed
|
||||
companion.session.reinit_session()
|
||||
# LANG: Frontier CAPI Access Token expired, trying to get a new one
|
||||
self.status['text'] = _('CAPI: Refreshing access token...')
|
||||
self.status['text'] = tr.tl('CAPI: Refreshing access token...')
|
||||
if companion.session.login():
|
||||
logger.debug('Initial query failed, but login() just worked, trying again...')
|
||||
companion.session.retrying = True
|
||||
@ -1366,7 +1363,7 @@ class AppWindow:
|
||||
|
||||
if not err: # not self.status['text']: # no errors
|
||||
# LANG: Time when we last obtained Frontier CAPI data
|
||||
self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(capi_response.query_time))
|
||||
self.status['text'] = strftime(tr.tl('Last updated at %H:%M:%S'), localtime(capi_response.query_time))
|
||||
|
||||
if capi_response.play_sound and play_bad:
|
||||
hotkeymgr.play_bad()
|
||||
@ -1394,9 +1391,9 @@ class AppWindow:
|
||||
return {
|
||||
None: '',
|
||||
'Idle': '',
|
||||
'FighterCon': _('Fighter'), # LANG: Multicrew role
|
||||
'FireCon': _('Gunner'), # LANG: Multicrew role
|
||||
'FlightCon': _('Helm'), # LANG: Multicrew role
|
||||
'FighterCon': tr.tl('Fighter'), # LANG: Multicrew role
|
||||
'FireCon': tr.tl('Gunner'), # LANG: Multicrew role
|
||||
'FlightCon': tr.tl('Helm'), # LANG: Multicrew role
|
||||
}.get(role, role)
|
||||
|
||||
if monitor.thread is None:
|
||||
@ -1419,7 +1416,7 @@ class AppWindow:
|
||||
else:
|
||||
self.cmdr['text'] = f'{monitor.cmdr}'
|
||||
|
||||
self.ship_label['text'] = _('Role') + ':' # LANG: Multicrew role label in main window
|
||||
self.ship_label['text'] = tr.tl('Role') + ':' # LANG: Multicrew role label in main window
|
||||
self.ship.configure(state=tk.NORMAL, text=crewroletext(monitor.state['Role']), url=None)
|
||||
|
||||
elif monitor.cmdr:
|
||||
@ -1429,7 +1426,7 @@ class AppWindow:
|
||||
else:
|
||||
self.cmdr['text'] = monitor.cmdr
|
||||
|
||||
self.ship_label['text'] = _('Ship') + ':' # LANG: 'Ship' label in main UI
|
||||
self.ship_label['text'] = tr.tl('Ship') + ':' # LANG: 'Ship' label in main UI
|
||||
|
||||
# TODO: Show something else when on_foot
|
||||
if monitor.state['ShipName']:
|
||||
@ -1452,7 +1449,7 @@ class AppWindow:
|
||||
|
||||
else:
|
||||
self.cmdr['text'] = ''
|
||||
self.ship_label['text'] = _('Ship') + ':' # LANG: 'Ship' label in main UI
|
||||
self.ship_label['text'] = tr.tl('Ship') + ':' # LANG: 'Ship' label in main UI
|
||||
self.ship['text'] = ''
|
||||
|
||||
if monitor.cmdr and monitor.is_beta:
|
||||
@ -1589,7 +1586,7 @@ class AppWindow:
|
||||
try:
|
||||
companion.session.auth_callback()
|
||||
# LANG: Successfully authenticated with the Frontier website
|
||||
self.status['text'] = _('Authentication successful')
|
||||
self.status['text'] = tr.tl('Authentication successful')
|
||||
self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status
|
||||
self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data
|
||||
|
||||
@ -1675,10 +1672,10 @@ class AppWindow:
|
||||
# Update button in main window
|
||||
cooldown_time = int(self.capi_query_holdoff_time - time())
|
||||
# LANG: Cooldown on 'Update' button
|
||||
self.button['text'] = self.theme_button['text'] = _('cooldown {SS}s').format(SS=cooldown_time)
|
||||
self.button['text'] = self.theme_button['text'] = tr.tl('cooldown {SS}s').format(SS=cooldown_time)
|
||||
self.w.after(1000, self.cooldown)
|
||||
else:
|
||||
self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window
|
||||
self.button['text'] = self.theme_button['text'] = tr.tl('Update') # LANG: Update button in main window
|
||||
self.button['state'] = self.theme_button['state'] = (
|
||||
monitor.cmdr and
|
||||
monitor.mode and
|
||||
@ -1743,7 +1740,7 @@ class AppWindow:
|
||||
|
||||
self.parent = parent
|
||||
# LANG: Help > About App
|
||||
self.title(_('About {APP}').format(APP=applongname))
|
||||
self.title(tr.tl('About {APP}').format(APP=applongname))
|
||||
|
||||
if parent.winfo_viewable():
|
||||
self.transient(parent)
|
||||
@ -1780,7 +1777,7 @@ class AppWindow:
|
||||
self.appversion_label.config(state=tk.DISABLED, bg=frame.cget("background"), font="TkDefaultFont")
|
||||
self.appversion_label.grid(row=row, column=0, sticky=tk.E)
|
||||
# LANG: Help > Release Notes
|
||||
self.appversion = HyperlinkLabel(frame, compound=tk.RIGHT, text=_('Release Notes'),
|
||||
self.appversion = HyperlinkLabel(frame, compound=tk.RIGHT, text=tr.tl('Release Notes'),
|
||||
url='https://github.com/EDCD/EDMarketConnector/releases/tag/Release/'
|
||||
f'{appversion_nobuild()}',
|
||||
underline=True)
|
||||
@ -1806,7 +1803,7 @@ class AppWindow:
|
||||
ttk.Label(frame).grid(row=row, column=0) # spacer
|
||||
row += 1
|
||||
# LANG: Generic 'OK' button label
|
||||
button = ttk.Button(frame, text=_('OK'), command=self.apply)
|
||||
button = ttk.Button(frame, text=tr.tl('OK'), command=self.apply)
|
||||
button.grid(row=row, column=2, sticky=tk.E)
|
||||
button.bind("<Return>", lambda event: self.apply())
|
||||
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
||||
@ -1874,7 +1871,7 @@ class AppWindow:
|
||||
|
||||
# Let the user know we're shutting down.
|
||||
# LANG: The application is shutting down
|
||||
self.status['text'] = _('Shutting down...')
|
||||
self.status['text'] = tr.tl('Shutting down...')
|
||||
self.w.update_idletasks()
|
||||
logger.info('Starting shutdown procedures...')
|
||||
|
||||
@ -2059,10 +2056,10 @@ def validate_providers():
|
||||
return
|
||||
|
||||
# LANG: Popup-text about Reset Providers
|
||||
popup_text = _(r'One or more of your URL Providers were invalid, and have been reset:\r\n\r\n')
|
||||
popup_text = tr.tl(r'One or more of your URL Providers were invalid, and have been reset:\r\n\r\n')
|
||||
for provider in reset_providers:
|
||||
# LANG: Text About What Provider Was Reset
|
||||
popup_text += _(r'{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n')
|
||||
popup_text += tr.tl(r'{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n')
|
||||
popup_text = popup_text.format(
|
||||
PROVIDER=provider,
|
||||
OLDPROV=reset_providers[provider][0],
|
||||
@ -2074,7 +2071,7 @@ def validate_providers():
|
||||
|
||||
tk.messagebox.showinfo(
|
||||
# LANG: Popup window title for Reset Providers
|
||||
_('EDMC: Default Providers Reset'),
|
||||
tr.tl('EDMC: Default Providers Reset'),
|
||||
popup_text
|
||||
)
|
||||
|
||||
@ -2200,7 +2197,7 @@ sys.path: {sys.path}'''
|
||||
# Plain, not via `logger`
|
||||
print(f'{applongname} {appversion()}')
|
||||
|
||||
Translations.install(config.get_str('language')) # Can generate errors so wait til log set up
|
||||
tr.install(config.get_str('language')) # Can generate errors so wait til log set up
|
||||
|
||||
setup_killswitches(args.killswitches_file)
|
||||
|
||||
@ -2235,7 +2232,7 @@ sys.path: {sys.path}'''
|
||||
"""Display message about 'broken' plugins that failed to load."""
|
||||
if plug.PLUGINS_broken:
|
||||
# LANG: Popup-text about 'broken' plugins that failed to load
|
||||
popup_text = _(
|
||||
popup_text = tr.tl(
|
||||
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' "
|
||||
"tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py "
|
||||
r"file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by "
|
||||
@ -2244,9 +2241,9 @@ sys.path: {sys.path}'''
|
||||
|
||||
# Substitute in the other words.
|
||||
popup_text = popup_text.format(
|
||||
PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab
|
||||
FILE=_('File'), # LANG: 'File' menu
|
||||
SETTINGS=_('Settings'), # LANG: File > Settings
|
||||
PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab
|
||||
FILE=tr.tl('File'), # LANG: 'File' menu
|
||||
SETTINGS=tr.tl('Settings'), # LANG: File > Settings
|
||||
DISABLED='.disabled'
|
||||
)
|
||||
# And now we do need these to be actual \r\n
|
||||
@ -2255,7 +2252,7 @@ sys.path: {sys.path}'''
|
||||
|
||||
tk.messagebox.showinfo(
|
||||
# LANG: Popup window title for list of 'broken' plugins that failed to load
|
||||
_('EDMC: Broken Plugins'),
|
||||
tr.tl('EDMC: Broken Plugins'),
|
||||
popup_text
|
||||
)
|
||||
|
||||
@ -2264,7 +2261,7 @@ sys.path: {sys.path}'''
|
||||
plugins_not_py3_last = config.get_int('plugins_not_py3_last', default=0)
|
||||
if (plugins_not_py3_last + 86400) < int(time()) and plug.PLUGINS_not_py3:
|
||||
# LANG: Popup-text about 'active' plugins without Python 3.x support
|
||||
popup_text = _(
|
||||
popup_text = tr.tl(
|
||||
"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the "
|
||||
"list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an "
|
||||
"updated version available, else alert the developer that they need to update the code for "
|
||||
@ -2274,9 +2271,9 @@ sys.path: {sys.path}'''
|
||||
|
||||
# Substitute in the other words.
|
||||
popup_text = popup_text.format(
|
||||
PLUGINS=_('Plugins'), # LANG: Settings > Plugins tab
|
||||
FILE=_('File'), # LANG: 'File' menu
|
||||
SETTINGS=_('Settings'), # LANG: File > Settings
|
||||
PLUGINS=tr.tl('Plugins'), # LANG: Settings > Plugins tab
|
||||
FILE=tr.tl('File'), # LANG: 'File' menu
|
||||
SETTINGS=tr.tl('Settings'), # LANG: File > Settings
|
||||
DISABLED='.disabled'
|
||||
)
|
||||
# And now we do need these to be actual \r\n
|
||||
@ -2285,7 +2282,7 @@ sys.path: {sys.path}'''
|
||||
|
||||
tk.messagebox.showinfo(
|
||||
# LANG: Popup window title for list of 'enabled' plugins that don't work with Python 3.x
|
||||
_('EDMC: Plugins Without Python 3.x Support'),
|
||||
tr.tl('EDMC: Plugins Without Python 3.x Support'),
|
||||
popup_text
|
||||
)
|
||||
config.set('plugins_not_py3_last', int(time()))
|
||||
@ -2298,7 +2295,7 @@ sys.path: {sys.path}'''
|
||||
if fdevid_file.is_file():
|
||||
continue
|
||||
# LANG: Popup-text about missing FDEVID Files
|
||||
popup_text = _(
|
||||
popup_text = tr.tl(
|
||||
"FDevID Files not found! Some functionality regarding commodities "
|
||||
r"may be disabled.\r\n\r\n Do you want to open the Wiki page on "
|
||||
"how to set up submodules?"
|
||||
@ -2309,7 +2306,7 @@ sys.path: {sys.path}'''
|
||||
|
||||
openwikipage = tk.messagebox.askquestion(
|
||||
# LANG: Popup window title for missing FDEVID files
|
||||
_('FDevIDs: Missing Commodity Files'),
|
||||
tr.tl('FDevIDs: Missing Commodity Files'),
|
||||
popup_text
|
||||
)
|
||||
if openwikipage == "yes":
|
||||
|
@ -231,6 +231,18 @@
|
||||
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||
"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 file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "有効にしている1つ以上のプラグインがロードに失敗しました。'{FILE}' > '{SETTINGS}' メニューで表示される設定ダイアログの'{PLUGINS}' タブの一覧を確認してください。この問題は誤ったフォルダ構造によって引き起こされます。load.pyファイルはplugins/プラグイン名/plug-in.pyとして配置される必要があります。\n\nプラグインを無効にするにはフォルダ名の最後に'{DISABLED}'を追加してください。";
|
||||
|
||||
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "一つ以上のURLプロバイダが無効であったためリセットされました:\n\n";
|
||||
|
||||
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} は {OLDPROV} に設定され、 {NEWPROV} にリセットされました\n";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||
"EDMC: Default Providers Reset" = "EDMC: デフォルトプロバイダーのリセット";
|
||||
|
||||
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||
"Awaiting Full CMDR Login" = "完全なCMDRログインを待機しています";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "ジャーナルディレクトリは既にロックされています";
|
||||
|
||||
@ -789,3 +801,5 @@
|
||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||
"Ships" = "所有船";
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} があります";
|
||||
|
@ -213,12 +213,36 @@
|
||||
/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
|
||||
"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Co najmniej jeden z uruchomionych pluginów nie wspiera Python 3.x. Sprawdź listę w '{FILE}' > '{SETTINGS}', sekcja '{PLUGINS}'. Upewnij się, że dostępna jest aktualna wersja pluginu, w przeciwnym razie poinformuj twórcę, że należy zaktualizować kod do wersji Python 3.x.\n\nMożesz wyłączyć plugin, dodając '{DISABLED}' na koniec nazwy jego folderu.";
|
||||
|
||||
/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
|
||||
"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "Pliki FDevID nie znalezione. Część funkcjonalności związanej z materiałami może nie działać. \n\nChcesz otworzyć stronę Wiki dotyczacą konfiguracji podmodułów?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Brakuje plików z towarami.";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Pluginy";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
|
||||
"EDMC: Plugins Without Python 3.x Support" = "EDMC: Pluginy Nie Wspierające Python 3.x";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
|
||||
"EDMC: Broken Plugins" = "EDMC: Uszkodzone pluginy";
|
||||
|
||||
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||
"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 file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jeden lub więcej plugin nie został załadowany. Sprawdź listę na zakładce '{PLUGINS}' w menu '{FILE}' > '{SETTINGS}'. Może to być spowodowane błedną strukturą katalogów. Plik load.py powinien być umiezczony w plugins/NAZWA PLUGINA/load.py.\n\nMożesz wyłączyć plugin zmieniając nazwę jego folderu tak, aby zawierała '{DISABLED}' na końcu nazwy.";
|
||||
|
||||
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Jeden lub więcej adres URL jest błędny i został zresetowany:\n";
|
||||
|
||||
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} ustawiony na {OLDPROV} i został zresetowany do {NEWPROV}\n";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||
"EDMC: Default Providers Reset" = "EDMC: Dostawca domyślny zresetowany";
|
||||
|
||||
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||
"Awaiting Full CMDR Login" = "Oczekiwanie na pełne zalogowanie do gry";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "Katalog dziennika zablokowany";
|
||||
|
||||
@ -471,6 +495,9 @@
|
||||
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
|
||||
"Information on migrating plugins" = "Informacja o migracji pluginów";
|
||||
|
||||
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
|
||||
"Broken Plugins" = "Niedziałające pluginy";
|
||||
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Pluginy wyłączone";
|
||||
|
||||
@ -774,3 +801,5 @@
|
||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||
"Ships" = "Statki";
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "Dostępna nowa wersja {NEWVER}";
|
||||
|
@ -213,12 +213,36 @@
|
||||
/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
|
||||
"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Um ou mais dos plugins habilitados não possuem suporte ao Python 3.x. Você pode ver a lista na guia '{PLUGINS}' em '{FILE}' > '{SETTINGS}'. Você deve verificar se existe versão atualizada ou então avisar o desenvolvedor de que ele precisa atualizar o código para o Python 3.x.\n\nVocê pode desabilitar um plugin renomeando sua pasta para que seu nome termine com '{DISABLED}'.";
|
||||
|
||||
/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
|
||||
"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "Arquivo FDevID não encontrados! Algumas funções de mercadorias podem estar indisponíveis.\n\nGostaria de abrir a Wiki com instruções para configurar sub-módulos?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Arquivos de Mercadorias não encontrados";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Plugins";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
|
||||
"EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins sem Suporte ao Python 3.x";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
|
||||
"EDMC: Broken Plugins" = "EDMC: Plugins Quebrados";
|
||||
|
||||
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
|
||||
"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 file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Um ou mais plugins ativados falhou ao carregar. Verifique a lista na aba '{PLUGINS}', em '{FILE}' > '{SETTINGS}'. Isto pode ter ocorrido por um erro na estrutura de pastas. O arquivo load.py deve estar localizado em plugins/NOME_PLUGIN/load.py.\n\nVocê pode desativar plugins renomeando a pasta para ter '.{DISABLED}' ao final do nome.";
|
||||
|
||||
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
|
||||
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Um ou mais dos seus Provedores de URL eram inválidos e portanto foram reconfigurados:\n";
|
||||
|
||||
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
|
||||
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} estava como {OLDPROV}, e foi reconfigurado para {NEWPROV}\n";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||
"EDMC: Default Providers Reset" = "EDMC: Provedores Padrão Reconfigurados";
|
||||
|
||||
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||
"Awaiting Full CMDR Login" = "Aguardando CMDT entrar no jogo";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "Diretório de Jornais já está bloqueado";
|
||||
|
||||
@ -471,6 +495,9 @@
|
||||
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
|
||||
"Information on migrating plugins" = "Informações de migração de plugins";
|
||||
|
||||
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
|
||||
"Broken Plugins" = "Plugins Quebrados";
|
||||
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Plugins desabilitados";
|
||||
|
||||
@ -774,3 +801,5 @@
|
||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||
"Ships" = "Naves";
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} está disponível";
|
||||
|
117
PLUGINS.md
117
PLUGINS.md
@ -31,7 +31,7 @@ then you'll need to be using an appropriate version of Python. The current
|
||||
version is listed in the
|
||||
[Environment section of Releasing.md](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md#environment).
|
||||
If you're developing your plugin simply against an install of EDMarketConnector
|
||||
then you'll be relying on the bundled version of Python (it's baked
|
||||
then you'll be relying on the bundled version of Python (it's baked
|
||||
into the .exe via the py2exe build process).
|
||||
|
||||
Please be sure to read the [Avoiding potential pitfalls](#avoiding-potential-pitfalls)
|
||||
@ -40,21 +40,21 @@ EDMarketConnector code including whole application crashes.
|
||||
|
||||
## Being aware of core application changes
|
||||
|
||||
It is highly advisable to ensure you are aware of all EDMarketConnector
|
||||
releases, including the pre-releases. The -beta and -rc changelogs will
|
||||
It is highly advisable to ensure you are aware of all EDMarketConnector
|
||||
releases, including the pre-releases. The -beta and -rc changelogs will
|
||||
contain valuable information about any forthcoming changes that affect plugins.
|
||||
The easiest way is:
|
||||
|
||||
1. Login to [GitHub](https://github.com).
|
||||
2. Navigate to [EDMarketConnector](https://github.com/EDCD/EDMarketConnector).
|
||||
3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on
|
||||
us). It's currently (2021-05-13) the left-most button of 3 near the
|
||||
3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on
|
||||
us). It's currently (2021-05-13) the left-most button of 3 near the
|
||||
top-right of the page.
|
||||
4. Click 'Custom'.
|
||||
5. Ensure 'Releases' is selected.
|
||||
6. Click 'Apply'.
|
||||
|
||||
And, of course, either ensure you check your GitHub messages regularly, or
|
||||
And, of course, either ensure you check your GitHub messages regularly, or
|
||||
have it set up to email you such notifications.
|
||||
|
||||
You should also keep an eye on [our GitHub Discussions](https://github.com/EDCD/EDMarketConnector/discussions)
|
||||
@ -113,13 +113,13 @@ from the original files unless specified as allowed in this section.
|
||||
Use `monitor.game_running()` as follows in case a plugin needs to know if we
|
||||
think the game is running. *NB: This is a function, and should be called as
|
||||
such. Using the bare word `game_running` will always be `True`.*
|
||||
|
||||
|
||||
```
|
||||
from monitor import monitor
|
||||
...
|
||||
if monitor.game_running():
|
||||
...
|
||||
```
|
||||
```
|
||||
|
||||
Use `monitor.is_live_galaxy()` to determine if the player is playing in the
|
||||
Live galaxy. Note the implementation details of this. At time of writing it
|
||||
@ -135,7 +135,7 @@ append a string to call out your plugin if you wish).
|
||||
`from ttkHyperlinkLabel import HyperlinkLabel` and `import myNotebook as nb` -
|
||||
For creating UI elements.
|
||||
|
||||
In addition to the above we also explicitly package the following python
|
||||
In addition to the above we also explicitly package the following python
|
||||
modules for plugin use:
|
||||
|
||||
- shutil
|
||||
@ -252,7 +252,7 @@ include variables, and even the returns of functions, in the output.
|
||||
|
||||
## Checking core EDMC version
|
||||
|
||||
If you have code that needs to act differently under different versions of
|
||||
If you have code that needs to act differently under different versions of
|
||||
this application then you can check utilise `config.appversion`.
|
||||
|
||||
Prior to version 5.0.0 this was a simple string. From 5.0.0 onwards it is,
|
||||
@ -313,7 +313,7 @@ Mac, and `$TMP/EDMarketConnector.log` on Linux.
|
||||
|
||||
## Avoiding potential pitfalls
|
||||
|
||||
There are a number of things that your code should either do or avoiding
|
||||
There are a number of things that your code should either do or avoiding
|
||||
doing so as to play nicely with the core EDMarketConnector code and not risk
|
||||
causing application crashes or hangs.
|
||||
|
||||
@ -324,12 +324,12 @@ See the section on [packaging extra modules](#your-plugin-directory-name-must-be
|
||||
|
||||
### Use a thread for long-running code
|
||||
|
||||
By default, your plugin code will be running in the main thread. So, if you
|
||||
perform some operation that takes significant time (more than a second) you
|
||||
will be blocking both the core code from continuing *and* any other plugins
|
||||
By default, your plugin code will be running in the main thread. So, if you
|
||||
perform some operation that takes significant time (more than a second) you
|
||||
will be blocking both the core code from continuing *and* any other plugins
|
||||
from running their main-thread code.
|
||||
|
||||
This includes any connections to remote services, such as a website or
|
||||
This includes any connections to remote services, such as a website or
|
||||
remote database. So please place such code within its own thread.
|
||||
|
||||
See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py)
|
||||
@ -338,20 +338,20 @@ with a queue to send data, and telling the sub-thread to stop during shutdown.
|
||||
|
||||
### All tkinter calls in main thread
|
||||
|
||||
The only tkinter calls that should ever be made from a sub-thread are
|
||||
The only tkinter calls that should ever be made from a sub-thread are
|
||||
`event_generate()` calls to send data back to the main thread.
|
||||
|
||||
Any attempt to manipulate tkinter UI elements directly from a sub-thread
|
||||
Any attempt to manipulate tkinter UI elements directly from a sub-thread
|
||||
will most likely crash the whole program.
|
||||
|
||||
See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py)
|
||||
code for an example of using `event_generate()` to cause the plugin main
|
||||
thread code to update a UI element. Start from the `plugin_app()`
|
||||
code for an example of using `event_generate()` to cause the plugin main
|
||||
thread code to update a UI element. Start from the `plugin_app()`
|
||||
implementation.
|
||||
|
||||
### Do not call tkinter `event_generate` during shutdown.
|
||||
|
||||
However, you must **not** make *any* tkinter `event_generate()` call whilst
|
||||
However, you must **not** make *any* tkinter `event_generate()` call whilst
|
||||
the application is shutting down.
|
||||
|
||||
The application shutdown sequence is itself triggered from the `<<Quit>>` event
|
||||
@ -359,8 +359,8 @@ handler, and generating another event from any code in, or called from,
|
||||
there causes the application to hang somewhere in the tk libraries.
|
||||
|
||||
You can detect if the application is shutting down with the boolean
|
||||
`config.shutting_down`. Note that although this is technically a function
|
||||
its implementation is of a property on `config.AbstractConfig` and thus you
|
||||
`config.shutting_down`. Note that although this is technically a function
|
||||
its implementation is of a property on `config.AbstractConfig` and thus you
|
||||
should treat it as a variable.
|
||||
|
||||
**Do NOT use**:
|
||||
@ -372,7 +372,7 @@ should treat it as a variable.
|
||||
# During shutdown
|
||||
```
|
||||
|
||||
as this will cause the 'During shutdown' branch to *always* be taken, as in
|
||||
as this will cause the 'During shutdown' branch to *always* be taken, as in
|
||||
this context you're testing if the function exists, and that is always True.
|
||||
|
||||
So instead use:
|
||||
@ -417,8 +417,8 @@ your plugin's settings in a platform-independent way. Previously this was done
|
||||
with a single set and two get methods, the new methods provide better type
|
||||
safety.
|
||||
|
||||
If you want to maintain compatibility with pre-5.0.0 versions of this
|
||||
application (please encourage plugin users to update!) then you'll need to
|
||||
If you want to maintain compatibility with pre-5.0.0 versions of this
|
||||
application (please encourage plugin users to update!) then you'll need to
|
||||
include this code in at least once in your plugin (no harm in putting it in
|
||||
all modules/files):
|
||||
|
||||
@ -699,8 +699,8 @@ cause `state['NavRoute'] = None`, but if you open the galaxy map in-game and
|
||||
cause an automatic re-plot of last route, then a new `NavRoute` event will
|
||||
also be generated and passed to plugins.
|
||||
|
||||
[2] - Some data from the CAPI is sometimes returned as a `list` (when all
|
||||
members are present) and other times as an integer-keyed `dict` (when at
|
||||
[2] - Some data from the CAPI is sometimes returned as a `list` (when all
|
||||
members are present) and other times as an integer-keyed `dict` (when at
|
||||
least one member is missing, so the indices are not contiguous). We choose to
|
||||
always convert to the integer-keyed `dict` form so that code utilising the data
|
||||
is simpler.
|
||||
@ -751,7 +751,7 @@ Journal `ModuleInfo` event.
|
||||
`OnFoot` is an indication as to if the player is on-foot, rather than in a
|
||||
vehicle.
|
||||
|
||||
`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your
|
||||
`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your
|
||||
Odyssey MicroResources in your Ship Locker. `BacKPack` contains `dict`s for
|
||||
the same when you're on-foot.
|
||||
|
||||
@ -760,10 +760,10 @@ relating to suits and their loadouts.
|
||||
|
||||
New in version 5.0.1:
|
||||
|
||||
`Odyssey` boolean based on the presence of such a flag in the `LoadGame`
|
||||
`Odyssey` boolean based on the presence of such a flag in the `LoadGame`
|
||||
event. Defaults to `False`, i.e. if no such key in the event.
|
||||
|
||||
The previously undocumented `Horizons` boolean is similarly from `LoadGame`,
|
||||
The previously undocumented `Horizons` boolean is similarly from `LoadGame`,
|
||||
but blindly retrieves the value rather than having a strict default. There'd
|
||||
be an exception if it wasn't there, and the value would be `None`. Note that
|
||||
this is **NOT** the same as the return from
|
||||
@ -821,7 +821,7 @@ if that's what was in the file.
|
||||
|
||||
New in version 5.8.0:
|
||||
|
||||
`StarPos`, `SystemAddress`, `SystemName` and `SystemPopulation` have been
|
||||
`StarPos`, `SystemAddress`, `SystemName` and `SystemPopulation` have been
|
||||
added to the `state` dictionary. Best efforts data pertaining to the star
|
||||
system the player is in.
|
||||
|
||||
@ -853,8 +853,8 @@ react to either in your plugin code then either compare in a case insensitive
|
||||
manner or check for both. The difference in case allows you to differentiate
|
||||
between the two scenarios.
|
||||
|
||||
**NB: Any of these events are passing to `journal_entry_cqc` rather than to
|
||||
`journal_entry` if player has loaded into Arena (CQC).**
|
||||
**NB: Any of these events are passing to `journal_entry_cqc` rather than to
|
||||
`journal_entry` if player has loaded into Arena (CQC).**
|
||||
|
||||
This event is not sent when EDMarketConnector is running on a different
|
||||
machine so you should not *rely* on receiving this event.
|
||||
@ -871,15 +871,15 @@ Examples of this are:
|
||||
|
||||
1. Every `NavRoute` event contains the full `Route` array as loaded from
|
||||
`NavRoute.json`.
|
||||
|
||||
|
||||
*NB: There is no indication available when a player cancels a route.* The
|
||||
game itself does not provide any such, not in a Journal event, not in a
|
||||
`Status.json` flag.
|
||||
|
||||
|
||||
The Journal documentation v28 is incorrect about the event
|
||||
and file being `Route(.json)` the word is `NavRoute`. Also the format of
|
||||
the data is, e.g.
|
||||
|
||||
|
||||
```json
|
||||
{ "timestamp":"2021-03-10T11:31:37Z",
|
||||
"event":"NavRoute",
|
||||
@ -893,9 +893,9 @@ Examples of this are:
|
||||
```
|
||||
|
||||
1. Every `ModuleInfo` event contains the full data as loaded from the
|
||||
`ModulesInfo.json` file. Note that we use the singular form here to
|
||||
`ModulesInfo.json` file. Note that we use the singular form here to
|
||||
stay consistent with the Journal event name.
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Journal entry in CQC
|
||||
@ -955,7 +955,7 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]):
|
||||
sys.stderr.write("Hardpoints {}\n".format(is_deployed and "deployed" or "stowed"))
|
||||
```
|
||||
|
||||
`dashboard_entry()` is called with the latest data from the `Status.json`
|
||||
`dashboard_entry()` is called with the latest data from the `Status.json`
|
||||
file when an update to that file is detected.
|
||||
|
||||
This will be when something on the player's cockpit display changes -
|
||||
@ -1193,19 +1193,34 @@ widget if you need to display routine status information.
|
||||
## Localisation
|
||||
|
||||
You can localise your plugin to one of the languages that EDMarketConnector
|
||||
itself supports. Add the following boilerplate near the top of each source
|
||||
itself supports. Add the following boilerplate near the top of the source
|
||||
file that contains strings that needs translating:
|
||||
|
||||
```python
|
||||
import l10n
|
||||
import functools
|
||||
_ = functools.partial(l10n.Translations.translate, context=__file__)
|
||||
plugin_tl = functools.partial(l10n.translations.tl, context=__file__)
|
||||
|
||||
```
|
||||
|
||||
Wrap each string that needs translating with the `_()` function, e.g.:
|
||||
Wrap each string that needs translating with the `plugin_tl()` function, e.g.:
|
||||
|
||||
```python
|
||||
somewidget["text"] = _("Happy!")
|
||||
somewidget["text"] = plugin_tl("Happy!")
|
||||
```
|
||||
|
||||
Note that you can name the "plugin_tl" function whatever you want - just make sure to stay consistent!
|
||||
Many plugins use `_` as the singleton name. We discourage that in versions 5.11 onward, but it should still work.
|
||||
If your plugin has multiple files that need translations, simply import the `plugin_tl` function to that location.
|
||||
You should only need to add the boilerplate once.
|
||||
|
||||
If you wish to override EDMCs current language when translating,
|
||||
`l10n.translations.tl()` also takes an optional `lang` parameter which can
|
||||
be passed a language identifier. For example to define a function to override
|
||||
all translations to German:
|
||||
|
||||
```python
|
||||
plugin_tl_de = functools.partial(l10n.Translations.translate, context=__file__, lang="de")
|
||||
```
|
||||
|
||||
If you display localized strings in EDMarketConnector's main window you should
|
||||
@ -1262,11 +1277,11 @@ Any modules the core application code uses will naturally be packaged, and
|
||||
we explicitly include a small number of additional modules for the use of
|
||||
plugins.
|
||||
|
||||
Whilst we would like to make all of the `stdlib` of Python available it is
|
||||
not automatically packaged into our releases by py2exe. We hope to address
|
||||
this in the 5.3 release series. In the meantime, if there's anything
|
||||
missing that you'd like to use, please ask. Yes, this very much means you
|
||||
need to test your plugins against a Windows installation of the application
|
||||
Whilst we would like to make all of the `stdlib` of Python available it is
|
||||
not automatically packaged into our releases by py2exe. We hope to address
|
||||
this in the 5.3 release series. In the meantime, if there's anything
|
||||
missing that you'd like to use, please ask. Yes, this very much means you
|
||||
need to test your plugins against a Windows installation of the application
|
||||
to be sure it will work.
|
||||
|
||||
See
|
||||
@ -1416,7 +1431,7 @@ versions of EDMarketConnector:
|
||||
[2to3](https://docs.python.org/3/library/2to3.html)
|
||||
tool can automate much of this work.
|
||||
|
||||
We advise *against* making any attempt to have a plugin's code work under
|
||||
both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based
|
||||
versions of this application, and you shouldn't support use of them with
|
||||
We advise *against* making any attempt to have a plugin's code work under
|
||||
both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based
|
||||
versions of this application, and you shouldn't support use of them with
|
||||
your plugin.
|
||||
|
29
companion.py
29
companion.py
@ -37,12 +37,11 @@ from config import config, user_agent
|
||||
from edmc_data import companion_category_map as category_map
|
||||
from EDMCLogging import get_main_logger
|
||||
from monitor import monitor
|
||||
from l10n import translations as tr
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str): return x
|
||||
|
||||
UserDict = collections.UserDict[str, Any] # indicate to our type checkers what this generic class holds normally
|
||||
else:
|
||||
UserDict = collections.UserDict # Otherwise simply use the actual class
|
||||
@ -224,7 +223,7 @@ class ServerError(Exception):
|
||||
self.args = args
|
||||
if not args:
|
||||
# LANG: Frontier CAPI didn't respond
|
||||
self.args = (_("Error: Frontier CAPI didn't respond"),)
|
||||
self.args = (tr.tl("Error: Frontier CAPI didn't respond"),)
|
||||
|
||||
|
||||
class ServerConnectionError(ServerError):
|
||||
@ -243,7 +242,7 @@ class ServerLagging(Exception):
|
||||
self.args = args
|
||||
if not args:
|
||||
# LANG: Frontier CAPI data doesn't agree with latest Journal game location
|
||||
self.args = (_('Error: Frontier server is lagging'),)
|
||||
self.args = (tr.tl('Error: Frontier server is lagging'),)
|
||||
|
||||
|
||||
class NoMonitorStation(Exception):
|
||||
@ -259,7 +258,7 @@ class NoMonitorStation(Exception):
|
||||
self.args = args
|
||||
if not args:
|
||||
# LANG: Commander is docked at an EDO settlement, got out and back in, we forgot the station
|
||||
self.args = (_("Docked but unknown station: EDO Settlement?"),)
|
||||
self.args = (tr.tl("Docked but unknown station: EDO Settlement?"),)
|
||||
|
||||
|
||||
class CredentialsError(Exception):
|
||||
@ -269,7 +268,7 @@ class CredentialsError(Exception):
|
||||
self.args = args
|
||||
if not args:
|
||||
# LANG: Generic "something went wrong with Frontier Auth" error
|
||||
self.args = (_('Error: Invalid Credentials'),)
|
||||
self.args = (tr.tl('Error: Invalid Credentials'),)
|
||||
|
||||
|
||||
class CredentialsRequireRefresh(Exception):
|
||||
@ -294,7 +293,7 @@ class CmdrError(Exception):
|
||||
self.args = args
|
||||
if not args:
|
||||
# LANG: Frontier CAPI authorisation not for currently game-active commander
|
||||
self.args = (_('Error: Wrong Cmdr'),)
|
||||
self.args = (tr.tl('Error: Wrong Cmdr'),)
|
||||
|
||||
|
||||
class Auth:
|
||||
@ -429,7 +428,7 @@ class Auth:
|
||||
'<unknown error>'
|
||||
)
|
||||
# LANG: Generic error prefix - following text is from Frontier auth service
|
||||
raise CredentialsError(f'{_("Error")}: {error!r}')
|
||||
raise CredentialsError(f'{tr.tl("Error")}: {error!r}')
|
||||
|
||||
r = None
|
||||
try:
|
||||
@ -472,18 +471,18 @@ class Auth:
|
||||
if (usr := data_decode.get('usr')) is None:
|
||||
logger.error('No "usr" in /decode data')
|
||||
# LANG: Frontier auth, no 'usr' section in returned data
|
||||
raise CredentialsError(_("Error: Couldn't check token customer_id"))
|
||||
raise CredentialsError(tr.tl("Error: Couldn't check token customer_id"))
|
||||
|
||||
if (customer_id := usr.get('customer_id')) is None:
|
||||
logger.error('No "usr"->"customer_id" in /decode data')
|
||||
# LANG: Frontier auth, no 'customer_id' in 'usr' section in returned data
|
||||
raise CredentialsError(_("Error: Couldn't check token customer_id"))
|
||||
raise CredentialsError(tr.tl("Error: Couldn't check token customer_id"))
|
||||
|
||||
# All 'FID' seen in Journals so far have been 'F<id>'
|
||||
# Frontier, Steam and Epic
|
||||
if f'F{customer_id}' != monitor.state.get('FID'):
|
||||
# LANG: Frontier auth customer_id doesn't match game session FID
|
||||
raise CredentialsError(_("Error: customer_id doesn't match!"))
|
||||
raise CredentialsError(tr.tl("Error: customer_id doesn't match!"))
|
||||
|
||||
logger.info(f'Frontier CAPI Auth: New token for \"{self.cmdr}\"')
|
||||
cmdrs = config.get_list('cmdrs', default=[])
|
||||
@ -505,7 +504,7 @@ class Auth:
|
||||
self.dump(r)
|
||||
|
||||
# LANG: Failed to get Access Token from Frontier Auth service
|
||||
raise CredentialsError(_('Error: unable to get token')) from e
|
||||
raise CredentialsError(tr.tl('Error: unable to get token')) from e
|
||||
|
||||
logger.error(f"Frontier CAPI Auth: Can't get token for \"{self.cmdr}\"")
|
||||
self.dump(r)
|
||||
@ -514,7 +513,7 @@ class Auth:
|
||||
'<unknown error>'
|
||||
)
|
||||
# LANG: Generic error prefix - following text is from Frontier auth service
|
||||
raise CredentialsError(f'{_("Error")}: {error!r}')
|
||||
raise CredentialsError(f'{tr.tl("Error")}: {error!r}')
|
||||
|
||||
@staticmethod
|
||||
def invalidate(cmdr: str | None) -> None:
|
||||
@ -841,7 +840,7 @@ class Session:
|
||||
except Exception as e:
|
||||
logger.debug('Attempting GET', exc_info=e)
|
||||
# LANG: Frontier CAPI data retrieval failed
|
||||
raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e
|
||||
raise ServerError(f'{tr.tl("Frontier CAPI query failure")}: {capi_endpoint}') from e
|
||||
|
||||
if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data:
|
||||
logger.error('No commander in returned data')
|
||||
@ -874,7 +873,7 @@ class Session:
|
||||
if response.status_code == 418:
|
||||
# "I'm a teapot" - used to signal maintenance
|
||||
# LANG: Frontier CAPI returned 418, meaning down for maintenance
|
||||
raise ServerError(_("Frontier CAPI down for maintenance"))
|
||||
raise ServerError(tr.tl("Frontier CAPI down for maintenance"))
|
||||
|
||||
logger.exception('Frontier CAPI: Misc. Error')
|
||||
raise ServerError('Frontier CAPI: Misc. Error')
|
||||
|
@ -53,8 +53,7 @@ appcmdname = 'EDMC'
|
||||
# <https://semver.org/#semantic-versioning-specification-semver>
|
||||
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
||||
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
||||
_static_appversion = '5.10.4'
|
||||
|
||||
_static_appversion = '5.10.6'
|
||||
_cached_version: semantic_version.Version | None = None
|
||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 05b16a4c716980ea95a46d29205f7d3b1f957fb4
|
||||
Subproject commit 651aab5af6a22980a1f88dcbb9ed256244cd6dff
|
@ -8,15 +8,20 @@ Translations are handled on [OneSky](https://oneskyapp.com/), specifically in [t
|
||||
|
||||
### Setting it up in the code
|
||||
|
||||
#### Call `_(...)`
|
||||
#### Call `tr.tl(...)`
|
||||
If you add any new strings that appear in the application UI, e.g. new configuration options, then you should specify them as:
|
||||
|
||||
_('Text that appears in UI')
|
||||
`_()` is a special global function that then handles the translation, using its single argument, plus the configured language, to look up the appropriate text.
|
||||
tr.tl('Text that appears in UI')
|
||||
|
||||
In order to do this, you must add the following import:
|
||||
|
||||
`from l10n import translations as tr`
|
||||
|
||||
`tr.tl()` is a function that then handles the translation, using its single argument, plus the configured language, to look up the appropriate text.
|
||||
|
||||
If you need to specify something in the text that shouldn't be translated then use the form:
|
||||
|
||||
_('Some text with a {WORD} not translated').format(WORD='word')
|
||||
tr.tl('Some text with a {WORD} not translated').format(WORD='word')
|
||||
This way 'word' will always be used literally.
|
||||
|
||||
#### Add a LANG comment
|
||||
@ -28,8 +33,9 @@ end of the line in your usage**. If both comments exist, the one on the
|
||||
current line is preferred over the one above
|
||||
|
||||
```py
|
||||
from l10n import translations as tr
|
||||
# LANG: this says stuff.
|
||||
_('stuff')
|
||||
tr.tl('stuff')
|
||||
```
|
||||
|
||||
#### Edit `L10n/en.template` to add the phrase
|
||||
@ -43,7 +49,7 @@ e.g.
|
||||
"Authentication successful" = "Authentication successful";
|
||||
which matches with:
|
||||
|
||||
self.status['text'] = _('Authentication successful') # Successfully authenticated with the Frontier website
|
||||
self.status['text'] = tr.tl('Authentication successful') # Successfully authenticated with the Frontier website
|
||||
|
||||
and
|
||||
|
||||
@ -51,12 +57,12 @@ and
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name";
|
||||
which matches with:
|
||||
|
||||
nb.Label(plugsframe, text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')).grid( # Help text in settings
|
||||
nb.Label(plugsframe, text=tr.tl("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')).grid( # Help text in settings
|
||||
`{CR}` is handled in `l10n.py`, translating to a unicode `\n`. See the code in`l10n.py` for any other such special substitutions.
|
||||
|
||||
You can even use other translations within a given string, e.g.:
|
||||
|
||||
_("One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name.".format(PLUGINS=_('Plugins'), FILE=_('File'), SETTINGS=_('Settings'), DISABLED='.disabled'))
|
||||
tr.tl("One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name.".format(PLUGINS=tr.tl('Plugins'), FILE=tr.tl('File'), SETTINGS=tr.tl('Settings'), DISABLED='.disabled'))
|
||||
/* Popup body: Warning about plugins without Python 3.x support [EDMarketConnector.py] */
|
||||
"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name.";
|
||||
|
||||
|
@ -357,7 +357,7 @@ outfitting_standard_map = {
|
||||
'guardianpowerdistributor': 'Guardian Hybrid Power Distributor',
|
||||
'guardianpowerplant': 'Guardian Hybrid Power Plant',
|
||||
'hyperdrive': 'Frame Shift Drive',
|
||||
('hyperdrive', 'overcharge'): 'Frame Shift Drive',
|
||||
('hyperdrive', 'overcharge'): 'Frame Shift Drive (SCO)',
|
||||
'lifesupport': 'Life Support',
|
||||
# 'planetapproachsuite': handled separately
|
||||
'powerdistributor': 'Power Distributor',
|
||||
@ -501,6 +501,7 @@ ship_name_map = {
|
||||
'mamba': 'Mamba',
|
||||
'orca': 'Orca',
|
||||
'python': 'Python',
|
||||
'python_nx': 'Python Mk II',
|
||||
'scout': 'Taipan Fighter',
|
||||
'sidewinder': 'Sidewinder',
|
||||
'testbuggy': 'Scarab',
|
||||
|
@ -106,7 +106,7 @@ def export(data, filename=None) -> None: # noqa: C901, CCR001
|
||||
else:
|
||||
name = module['name'] # type: ignore
|
||||
|
||||
if name == 'Frame Shift Drive':
|
||||
if name == 'Frame Shift Drive' or name == 'Frame Shift Drive (SCO)':
|
||||
fsd = module # save for range calculation
|
||||
|
||||
if mods.get('OutfittingFieldType_FSDOptimalMass'):
|
||||
@ -167,15 +167,19 @@ def export(data, filename=None) -> None: # noqa: C901, CCR001
|
||||
try:
|
||||
mass += ships[ship_name_map[data['ship']['name'].lower()]]['hullMass']
|
||||
string += f'Mass : {mass:.2f} T empty\n {mass + fuel + cargo:.2f} T full\n'
|
||||
maxfuel = fsd.get('maxfuel', 0) # type: ignore
|
||||
fuelmul = fsd.get('fuelmul', 0) # type: ignore
|
||||
|
||||
multiplier = pow(min(fuel, fsd['maxfuel']) / fsd['fuelmul'], 1.0 # type: ignore
|
||||
/ fsd['fuelpower']) * fsd['optmass'] # type: ignore
|
||||
|
||||
range_unladen = multiplier / (mass + fuel) + jumpboost
|
||||
range_laden = multiplier / (mass + fuel + cargo) + jumpboost
|
||||
# As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with
|
||||
# a locale that uses comma for decimal separator.
|
||||
string += f'Range : {range_unladen:.2f} LY unladen\n {range_laden:.2f} LY laden\n'
|
||||
try:
|
||||
multiplier = pow(min(fuel, maxfuel) / fuelmul, 1.0 / fsd['fuelpower']) * fsd['optmass'] # type: ignore
|
||||
range_unladen = multiplier / (mass + fuel) + jumpboost
|
||||
range_laden = multiplier / (mass + fuel + cargo) + jumpboost
|
||||
# As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with
|
||||
# a locale that uses comma for decimal separator.
|
||||
except ZeroDivisionError:
|
||||
range_unladen = range_laden = 0.0
|
||||
string += (f'Range : {range_unladen:.2f} LY unladen\n'
|
||||
f' {range_laden:.2f} LY laden\n')
|
||||
|
||||
except Exception:
|
||||
if __debug__:
|
||||
|
@ -13,17 +13,13 @@ import tkinter as tk
|
||||
from enum import Enum
|
||||
from os import getpid as os_getpid
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
|
||||
from typing import Callable
|
||||
from l10n import translations as tr
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
def _(x: str) -> str:
|
||||
return x
|
||||
|
||||
|
||||
class JournalLockResult(Enum):
|
||||
"""Enumeration of possible outcomes of trying to lock the Journal Directory."""
|
||||
@ -212,7 +208,7 @@ class JournalLock:
|
||||
self.parent = parent
|
||||
self.callback = callback
|
||||
# LANG: Title text on popup when Journal directory already locked
|
||||
self.title(_('Journal directory already locked'))
|
||||
self.title(tr.tl('Journal directory already locked'))
|
||||
|
||||
# remove decoration
|
||||
if sys.platform == 'win32':
|
||||
@ -225,16 +221,17 @@ class JournalLock:
|
||||
|
||||
self.blurb = tk.Label(frame)
|
||||
# LANG: Text for when newly selected Journal directory is already locked
|
||||
self.blurb['text'] = _("The new Journal Directory location is already locked.{CR}"
|
||||
"You can either attempt to resolve this and then Retry, or choose to Ignore this.")
|
||||
self.blurb['text'] = tr.tl("The new Journal Directory location is already locked.{CR}"
|
||||
"You can either attempt to resolve this and then Retry, "
|
||||
"or choose to Ignore this.")
|
||||
self.blurb.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW)
|
||||
|
||||
# LANG: Generic 'Retry' button label
|
||||
self.retry_button = ttk.Button(frame, text=_('Retry'), command=self.retry)
|
||||
self.retry_button = ttk.Button(frame, text=tr.tl('Retry'), command=self.retry)
|
||||
self.retry_button.grid(row=2, column=0, sticky=tk.EW)
|
||||
|
||||
# LANG: Generic 'Ignore' button label
|
||||
self.ignore_button = ttk.Button(frame, text=_('Ignore'), command=self.ignore)
|
||||
self.ignore_button = ttk.Button(frame, text=tr.tl('Ignore'), command=self.ignore)
|
||||
self.ignore_button.grid(row=2, column=1, sticky=tk.EW)
|
||||
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
||||
|
||||
|
83
l10n.py
83
l10n.py
@ -20,12 +20,10 @@ from contextlib import suppress
|
||||
from os import listdir, sep, makedirs
|
||||
from os.path import basename, dirname, isdir, join, abspath, exists
|
||||
from typing import TYPE_CHECKING, Iterable, TextIO, cast
|
||||
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str: return x
|
||||
|
||||
# Note that this is also done in EDMarketConnector.py, and thus removing this here may not have a desired effect
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
@ -61,7 +59,16 @@ if sys.platform == 'win32':
|
||||
GetNumberFormatEx.restype = ctypes.c_int
|
||||
|
||||
|
||||
class _Translations:
|
||||
class Translations:
|
||||
"""
|
||||
The Translation System.
|
||||
|
||||
Contains all the logic needed to support multiple languages in EDMC.
|
||||
DO NOT USE THIS DIRECTLY UNLESS YOU KNOW WHAT YOU'RE DOING.
|
||||
In most cases, you'll want to import translations.
|
||||
|
||||
For most cases: from l10n import translations as tr.
|
||||
"""
|
||||
|
||||
FALLBACK = 'en' # strings in this code are in English
|
||||
FALLBACK_NAME = 'English'
|
||||
@ -79,6 +86,8 @@ class _Translations:
|
||||
Use when translation is not desired or not available
|
||||
"""
|
||||
self.translations = {None: {}}
|
||||
# WARNING: '_' is Deprecated. Will be removed in 6.0 or later.
|
||||
# Migrate to calling translations.translate or tr.tl directly.
|
||||
builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n')
|
||||
|
||||
def install(self, lang: str | None = None) -> None: # noqa: CCR001
|
||||
@ -88,7 +97,7 @@ class _Translations:
|
||||
:param lang: The language to translate to, defaults to the preferred language
|
||||
"""
|
||||
available = self.available()
|
||||
available.add(_Translations.FALLBACK)
|
||||
available.add(Translations.FALLBACK)
|
||||
if not lang:
|
||||
# Choose the default language
|
||||
for preferred in Locale.preferred_languages():
|
||||
@ -122,6 +131,8 @@ class _Translations:
|
||||
except Exception:
|
||||
logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}')
|
||||
|
||||
# WARNING: '_' is Deprecated. Will be removed in 6.0 or later.
|
||||
# Migrate to calling translations.translate or tr.tl directly.
|
||||
builtins.__dict__['_'] = self.translate
|
||||
|
||||
def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
|
||||
@ -135,12 +146,12 @@ class _Translations:
|
||||
|
||||
for line in h:
|
||||
if line.strip():
|
||||
match = _Translations.TRANS_RE.match(line)
|
||||
match = Translations.TRANS_RE.match(line)
|
||||
if match:
|
||||
to_set = match.group(2).replace(r'\"', '"').replace('{CR}', '\n')
|
||||
translations[match.group(1).replace(r'\"', '"')] = to_set
|
||||
|
||||
elif not _Translations.COMMENT_RE.match(line):
|
||||
elif not Translations.COMMENT_RE.match(line):
|
||||
logger.debug(f'Bad translation: {line.strip()}')
|
||||
h.close()
|
||||
|
||||
@ -149,21 +160,45 @@ class _Translations:
|
||||
|
||||
return translations
|
||||
|
||||
def translate(self, x: str, context: str | None = None) -> str:
|
||||
def tl(self, x: str, context: str | None = None, lang: str | None = None) -> str:
|
||||
"""Use the shorthand Dummy loader for the translate function."""
|
||||
return self.translate(x, context, lang)
|
||||
|
||||
def translate(self, x: str, context: str | None = None, lang: str | None = None) -> str: # noqa: CCR001
|
||||
"""
|
||||
Translate the given string to the current lang.
|
||||
Translate the given string to the current lang or an overriden lang.
|
||||
|
||||
:param x: The string to translate
|
||||
:param context: Whether or not to search the given directory for translation files, defaults to None
|
||||
:param context: Contains the full path to the file being localised, from which the plugin name is parsed and
|
||||
used to locate the plugin translation files, defaults to None
|
||||
:param lang: Contains a language code to override the EDMC language for this translation, defaults to None
|
||||
:return: The translated string
|
||||
"""
|
||||
plugin_name: str | None = None
|
||||
plugin_path: str | None = None
|
||||
|
||||
if context:
|
||||
# TODO: There is probably a better way to go about this now.
|
||||
context = context[len(config.plugin_dir)+1:].split(sep)[0]
|
||||
if self.translations[None] and context not in self.translations:
|
||||
logger.debug(f'No translations for {context!r}')
|
||||
plugin_name = context[len(config.plugin_dir)+1:].split(sep)[0]
|
||||
plugin_path = join(config.plugin_dir_path, plugin_name, LOCALISATION_DIR)
|
||||
|
||||
return self.translations.get(context, {}).get(x) or self.translate(x)
|
||||
if lang:
|
||||
contents: dict[str, str] = self.contents(lang=lang, plugin_path=plugin_path)
|
||||
|
||||
if not contents or type(contents) is not dict:
|
||||
logger.debug(f'Failure loading translations for overridden language {lang!r}')
|
||||
return self.translate(x)
|
||||
elif x not in contents.keys():
|
||||
logger.debug(f'Missing translation: {x!r} for overridden language {lang!r}')
|
||||
return self.translate(x)
|
||||
else:
|
||||
return contents.get(x) or self.translate(x)
|
||||
|
||||
if plugin_name:
|
||||
if self.translations[None] and plugin_name not in self.translations:
|
||||
logger.debug(f'No translations for {plugin_name!r}')
|
||||
|
||||
return self.translations.get(plugin_name, {}).get(x) or self.translate(x)
|
||||
|
||||
if self.translations[None] and x not in self.translations[None]:
|
||||
logger.debug(f'Missing translation: {x!r}')
|
||||
@ -182,11 +217,11 @@ class _Translations:
|
||||
"""Available language names by code."""
|
||||
names: dict[str | None, str] = {
|
||||
# LANG: The system default language choice in Settings > Appearance
|
||||
None: _('Default'), # Appearance theme and language setting
|
||||
None: self.tl('Default'), # Appearance theme and language setting
|
||||
}
|
||||
names.update(sorted(
|
||||
[(lang, self.contents(lang).get(LANGUAGE_ID, lang)) for lang in self.available()] +
|
||||
[(_Translations.FALLBACK, _Translations.FALLBACK_NAME)],
|
||||
[(Translations.FALLBACK, Translations.FALLBACK_NAME)],
|
||||
key=lambda x: x[1]
|
||||
)) # Sort by name
|
||||
|
||||
@ -324,9 +359,23 @@ class _Locale:
|
||||
|
||||
# singletons
|
||||
Locale = _Locale()
|
||||
Translations = _Translations()
|
||||
translations = Translations()
|
||||
|
||||
|
||||
# WARNING: 'Translations' singleton is deprecated. Will be removed in 6.0 or later.
|
||||
# Migrate to importing 'translations'.
|
||||
# Begin Deprecation Zone
|
||||
class _Translations(Translations):
|
||||
def __init__(self):
|
||||
logger.warning(DeprecationWarning('Translations and _Translations() are deprecated. '
|
||||
'Please use translations and Translations() instead.'))
|
||||
super().__init__()
|
||||
|
||||
|
||||
# Yes, I know this is awful renaming garbage. But we need it for compat.
|
||||
Translations: Translations = translations # type: ignore
|
||||
# End Deprecation Zone
|
||||
|
||||
# generate template strings file - like xgettext
|
||||
# parsing is limited - only single ' or " delimited strings, and only one string per line
|
||||
if __name__ == "__main__":
|
||||
|
240
modules.json
240
modules.json
@ -326,6 +326,9 @@
|
||||
"hpt_antiunknownshutdown_tiny": {
|
||||
"mass": 1.3
|
||||
},
|
||||
"hpt_antiunknownshutdown_tiny_v2": {
|
||||
"mass": 3
|
||||
},
|
||||
"hpt_atdumbfiremissile_fixed_large": {
|
||||
"mass": 8
|
||||
},
|
||||
@ -446,6 +449,9 @@
|
||||
"hpt_causticmissile_fixed_medium": {
|
||||
"mass": 4
|
||||
},
|
||||
"hpt_causticsinklauncher_turret_tiny": {
|
||||
"mass": 1.7
|
||||
},
|
||||
"hpt_chafflauncher_tiny": {
|
||||
"mass": 1.3
|
||||
},
|
||||
@ -833,6 +839,9 @@
|
||||
"hpt_slugshot_turret_small": {
|
||||
"mass": 2
|
||||
},
|
||||
"hpt_xenoscanner_advanced_tiny": {
|
||||
"mass": 3
|
||||
},
|
||||
"hpt_xenoscanner_basic_tiny": {
|
||||
"mass": 1.3
|
||||
},
|
||||
@ -1352,6 +1361,12 @@
|
||||
"int_engine_size8_class5": {
|
||||
"mass": 160
|
||||
},
|
||||
"int_expmodulestabiliser_size3_class3": {
|
||||
"mass": 8
|
||||
},
|
||||
"int_expmodulestabiliser_size5_class3": {
|
||||
"mass": 20
|
||||
},
|
||||
"int_fighterbay_size5_class1": {
|
||||
"mass": 20
|
||||
},
|
||||
@ -1750,6 +1765,216 @@
|
||||
"int_hullreinforcement_size5_class2": {
|
||||
"mass": 16
|
||||
},
|
||||
"int_hyperdrive_overcharge_size2_class1": {
|
||||
"mass": 2.5,
|
||||
"optmass": 60,
|
||||
"maxfuel": 0.6,
|
||||
"fuelmul": 0.008,
|
||||
"fuelpower": 2
|
||||
},
|
||||
"int_hyperdrive_overcharge_size2_class2": {
|
||||
"mass": 2.5,
|
||||
"optmass": 90,
|
||||
"maxfuel": 0.9,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2
|
||||
},
|
||||
"int_hyperdrive_overcharge_size2_class3": {
|
||||
"mass": 2.5,
|
||||
"optmass": 90,
|
||||
"maxfuel": 0.9,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2
|
||||
},
|
||||
"int_hyperdrive_overcharge_size2_class4": {
|
||||
"mass": 2.5,
|
||||
"optmass": 90,
|
||||
"maxfuel": 0.9,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2
|
||||
},
|
||||
"int_hyperdrive_overcharge_size2_class5": {
|
||||
"mass": 2.5,
|
||||
"optmass": 100,
|
||||
"maxfuel": 1,
|
||||
"fuelmul": 0.013,
|
||||
"fuelpower": 2
|
||||
},
|
||||
"int_hyperdrive_overcharge_size3_class1": {
|
||||
"mass": 5,
|
||||
"optmass": 100,
|
||||
"maxfuel": 1.2,
|
||||
"fuelmul": 0.008,
|
||||
"fuelpower": 2.15
|
||||
},
|
||||
"int_hyperdrive_overcharge_size3_class2": {
|
||||
"mass": 2,
|
||||
"optmass": 150,
|
||||
"maxfuel": 1.8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.15
|
||||
},
|
||||
"int_hyperdrive_overcharge_size3_class3": {
|
||||
"mass": 5,
|
||||
"optmass": 150,
|
||||
"maxfuel": 1.8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.15
|
||||
},
|
||||
"int_hyperdrive_overcharge_size3_class4": {
|
||||
"mass": 5,
|
||||
"optmass": 150,
|
||||
"maxfuel": 1.8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.15
|
||||
},
|
||||
"int_hyperdrive_overcharge_size3_class5": {
|
||||
"mass": 5,
|
||||
"optmass": 167,
|
||||
"maxfuel": 1.9,
|
||||
"fuelmul": 0.013,
|
||||
"fuelpower": 2.15
|
||||
},
|
||||
"int_hyperdrive_overcharge_size4_class1": {
|
||||
"mass": 10,
|
||||
"optmass": 350,
|
||||
"maxfuel": 2,
|
||||
"fuelmul": 0.008,
|
||||
"fuelpower": 2.3
|
||||
},
|
||||
"int_hyperdrive_overcharge_size4_class2": {
|
||||
"mass": 4,
|
||||
"optmass": 525,
|
||||
"maxfuel": 3,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.3
|
||||
},
|
||||
"int_hyperdrive_overcharge_size4_class3": {
|
||||
"mass": 10,
|
||||
"optmass": 525,
|
||||
"maxfuel": 3,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.3
|
||||
},
|
||||
"int_hyperdrive_overcharge_size4_class4": {
|
||||
"mass": 10,
|
||||
"optmass": 525,
|
||||
"maxfuel": 3,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.3
|
||||
},
|
||||
"int_hyperdrive_overcharge_size4_class5": {
|
||||
"mass": 10,
|
||||
"optmass": 585,
|
||||
"maxfuel": 3.2,
|
||||
"fuelmul": 0.013,
|
||||
"fuelpower": 2.3
|
||||
},
|
||||
"int_hyperdrive_overcharge_size5_class1": {
|
||||
"mass": 20,
|
||||
"optmass": 700,
|
||||
"maxfuel": 3.3,
|
||||
"fuelmul": 0.008,
|
||||
"fuelpower": 2.45
|
||||
},
|
||||
"int_hyperdrive_overcharge_size5_class2": {
|
||||
"mass": 8,
|
||||
"optmass": 1050,
|
||||
"maxfuel": 5,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.45
|
||||
},
|
||||
"int_hyperdrive_overcharge_size5_class3": {
|
||||
"mass": 20,
|
||||
"optmass": 1050,
|
||||
"maxfuel": 5,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.45
|
||||
},
|
||||
"int_hyperdrive_overcharge_size5_class4": {
|
||||
"mass": 20,
|
||||
"optmass": 1050,
|
||||
"maxfuel": 5,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.45
|
||||
},
|
||||
"int_hyperdrive_overcharge_size5_class5": {
|
||||
"mass": 20,
|
||||
"optmass": 1175,
|
||||
"maxfuel": 5.2,
|
||||
"fuelmul": 0.013,
|
||||
"fuelpower": 2.45
|
||||
},
|
||||
"int_hyperdrive_overcharge_size6_class1": {
|
||||
"mass": 40,
|
||||
"optmass": 1200,
|
||||
"maxfuel": 5.3,
|
||||
"fuelmul": 0.008,
|
||||
"fuelpower": 2.6
|
||||
},
|
||||
"int_hyperdrive_overcharge_size6_class2": {
|
||||
"mass": 16,
|
||||
"optmass": 1800,
|
||||
"maxfuel": 8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.6
|
||||
},
|
||||
"int_hyperdrive_overcharge_size6_class3": {
|
||||
"mass": 40,
|
||||
"optmass": 1800,
|
||||
"maxfuel": 8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.6
|
||||
},
|
||||
"int_hyperdrive_overcharge_size6_class4": {
|
||||
"mass": 40,
|
||||
"optmass": 1800,
|
||||
"maxfuel": 8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.6
|
||||
},
|
||||
"int_hyperdrive_overcharge_size6_class5": {
|
||||
"mass": 40,
|
||||
"optmass": 2000,
|
||||
"maxfuel": 8.3,
|
||||
"fuelmul": 0.013,
|
||||
"fuelpower": 2.6
|
||||
},
|
||||
"int_hyperdrive_overcharge_size7_class1": {
|
||||
"mass": 80,
|
||||
"optmass": 1800,
|
||||
"maxfuel": 8.5,
|
||||
"fuelmul": 0.008,
|
||||
"fuelpower": 2.75
|
||||
},
|
||||
"int_hyperdrive_overcharge_size7_class2": {
|
||||
"mass": 32,
|
||||
"optmass": 2700,
|
||||
"maxfuel": 12.8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.75
|
||||
},
|
||||
"int_hyperdrive_overcharge_size7_class3": {
|
||||
"mass": 80,
|
||||
"optmass": 2700,
|
||||
"maxfuel": 12.8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.75
|
||||
},
|
||||
"int_hyperdrive_overcharge_size7_class4": {
|
||||
"mass": 80,
|
||||
"optmass": 2700,
|
||||
"maxfuel": 12.8,
|
||||
"fuelmul": 0.012,
|
||||
"fuelpower": 2.75
|
||||
},
|
||||
"int_hyperdrive_overcharge_size7_class5": {
|
||||
"mass": 80,
|
||||
"optmass": 3000,
|
||||
"maxfuel": 13.1,
|
||||
"fuelmul": 0.013,
|
||||
"fuelpower": 2.75
|
||||
},
|
||||
"int_hyperdrive_size2_class1": {
|
||||
"mass": 2.5,
|
||||
"optmass": 48,
|
||||
@ -3138,6 +3363,21 @@
|
||||
"python_armour_reactive": {
|
||||
"mass": 53
|
||||
},
|
||||
"python_nx_armour_grade1": {
|
||||
"mass": 0
|
||||
},
|
||||
"python_nx_armour_grade2": {
|
||||
"mass": 26
|
||||
},
|
||||
"python_nx_armour_grade3": {
|
||||
"mass": 53
|
||||
},
|
||||
"python_nx_armour_mirrored": {
|
||||
"mass": 53
|
||||
},
|
||||
"python_nx_armour_reactive": {
|
||||
"mass": 53
|
||||
},
|
||||
"sidewinder_armour_grade1": {
|
||||
"mass": 0
|
||||
},
|
||||
|
@ -34,10 +34,6 @@ STARTUP = 'journal.startup'
|
||||
MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds
|
||||
MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str:
|
||||
return x
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
||||
|
@ -12,11 +12,8 @@ from __future__ import annotations
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
from typing import TYPE_CHECKING
|
||||
from PIL import ImageGrab
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str: return x
|
||||
from l10n import translations as tr
|
||||
|
||||
if sys.platform == 'win32':
|
||||
PAGEFG = 'SystemWindowText'
|
||||
@ -107,9 +104,10 @@ class EntryMenu(ttk.Entry):
|
||||
img = ImageGrab.grabclipboard()
|
||||
if img:
|
||||
# Hijack existing translation, yes it doesn't exactly match here.
|
||||
# LANG: Generic error prefix - following text is from Frontier auth service;
|
||||
messagebox.showwarning(_('Error'),
|
||||
_('Cannot paste non-text content.')) # LANG: Can't Paste Images or Files in Text
|
||||
messagebox.showwarning(
|
||||
tr.tl('Error'), # LANG: Generic error prefix - following text is from Frontier auth service;
|
||||
tr.tl('Cannot paste non-text content.') # LANG: Can't Paste Images or Files in Text
|
||||
)
|
||||
return
|
||||
text = self.clipboard_get()
|
||||
if self.selection_present() and text:
|
||||
|
@ -222,7 +222,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
|
||||
(new['class'], new['rating']) = (str(name[2][4:]), 'H')
|
||||
|
||||
elif len(name) > 4 and name[1] == 'hyperdrive': # e.g. Int_Hyperdrive_Overcharge_Size6_Class3
|
||||
(new['class'], new['rating']) = (str(name[4][-1:]), 'C')
|
||||
(new['class'], new['rating']) = (str(name[3][-1:]), rating_map[name[4][-1:]])
|
||||
|
||||
else:
|
||||
if len(name) < 3:
|
||||
@ -257,7 +257,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
|
||||
if not m:
|
||||
print(f'No data for module {key}')
|
||||
|
||||
elif new['name'] == 'Frame Shift Drive':
|
||||
elif new['name'] == 'Frame Shift Drive' or new['name'] == 'Frame Shift Drive (SCO)':
|
||||
assert 'mass' in m and 'optmass' in m and 'maxfuel' in m and 'fuelmul' in m and 'fuelpower' in m, m
|
||||
|
||||
else:
|
||||
|
@ -27,15 +27,11 @@ import io
|
||||
import json
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING
|
||||
import myNotebook as nb # noqa: N813 # its not my fault.
|
||||
from EDMCLogging import get_main_logger
|
||||
from plug import show_error
|
||||
from config import config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(s: str) -> str:
|
||||
...
|
||||
from l10n import translations as tr
|
||||
|
||||
|
||||
class CoriolisConfig:
|
||||
@ -45,15 +41,15 @@ class CoriolisConfig:
|
||||
self.normal_url = ''
|
||||
self.beta_url = ''
|
||||
self.override_mode = ''
|
||||
self.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto
|
||||
self.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal
|
||||
self.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta
|
||||
self.override_text_old_auto = tr.tl('Auto') # LANG: Coriolis normal/beta selection - auto
|
||||
self.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal
|
||||
self.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta
|
||||
|
||||
self.normal_textvar = tk.StringVar()
|
||||
self.beta_textvar = tk.StringVar()
|
||||
self.override_textvar = tk.StringVar()
|
||||
|
||||
def initialize_urls(self):
|
||||
def initialize_urls(self) -> None:
|
||||
"""Initialize Coriolis URLs and override mode from configuration."""
|
||||
self.normal_url = config.get_str('coriolis_normal_url', default=DEFAULT_NORMAL_URL)
|
||||
self.beta_url = config.get_str('coriolis_beta_url', default=DEFAULT_BETA_URL)
|
||||
@ -63,10 +59,10 @@ class CoriolisConfig:
|
||||
self.beta_textvar.set(value=self.beta_url)
|
||||
self.override_textvar.set(
|
||||
value={
|
||||
'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection
|
||||
'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection
|
||||
'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection
|
||||
}.get(self.override_mode, _('Auto')) # LANG: 'Auto' label for Coriolis site override selection
|
||||
'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection
|
||||
'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection
|
||||
'beta': tr.tl('Beta') # LANG: 'Beta' label for Coriolis site override selection
|
||||
}.get(self.override_mode, tr.tl('Auto')) # LANG: 'Auto' label for Coriolis site override selection
|
||||
)
|
||||
|
||||
|
||||
@ -91,38 +87,38 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
|
||||
BOXY = 2 # noqa: N806 # box spacing
|
||||
|
||||
# Save the old text values for the override mode, so we can update them if the language is changed
|
||||
coriolis_config.override_text_old_auto = _('Auto') # LANG: Coriolis normal/beta selection - auto
|
||||
coriolis_config.override_text_old_normal = _('Normal') # LANG: Coriolis normal/beta selection - normal
|
||||
coriolis_config.override_text_old_beta = _('Beta') # LANG: Coriolis normal/beta selection - beta
|
||||
coriolis_config.override_text_old_auto = tr.tl('Auto') # LANG: Coriolis normal/beta selection - auto
|
||||
coriolis_config.override_text_old_normal = tr.tl('Normal') # LANG: Coriolis normal/beta selection - normal
|
||||
coriolis_config.override_text_old_beta = tr.tl('Beta') # LANG: Coriolis normal/beta selection - beta
|
||||
|
||||
conf_frame = nb.Frame(parent)
|
||||
conf_frame.columnconfigure(index=1, weight=1)
|
||||
cur_row = 0
|
||||
# LANG: Settings>Coriolis: Help/hint for changing coriolis URLs
|
||||
nb.Label(conf_frame, text=_(
|
||||
nb.Label(conf_frame, text=tr.tl(
|
||||
"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='"
|
||||
)).grid(sticky=tk.EW, row=cur_row, column=0, padx=PADX, pady=PADY, columnspan=3)
|
||||
cur_row += 1
|
||||
|
||||
# LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
|
||||
nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
||||
nb.Label(conf_frame, text=tr.tl('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
||||
nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid(
|
||||
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
|
||||
)
|
||||
# LANG: Generic 'Reset' button label
|
||||
nb.Button(conf_frame, text=_("Reset"),
|
||||
nb.Button(conf_frame, text=tr.tl("Reset"),
|
||||
command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
|
||||
sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
|
||||
)
|
||||
cur_row += 1
|
||||
|
||||
# LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL
|
||||
nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
||||
nb.Label(conf_frame, text=tr.tl('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
|
||||
nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
|
||||
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
|
||||
)
|
||||
# LANG: Generic 'Reset' button label
|
||||
nb.Button(conf_frame, text=_('Reset'),
|
||||
nb.Button(conf_frame, text=tr.tl('Reset'),
|
||||
command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
|
||||
sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
|
||||
)
|
||||
@ -130,16 +126,16 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
|
||||
|
||||
# TODO: This needs a help/hint text to be sure users know what it's for.
|
||||
# LANG: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL
|
||||
nb.Label(conf_frame, text=_('Override Beta/Normal Selection')).grid(
|
||||
nb.Label(conf_frame, text=tr.tl('Override Beta/Normal Selection')).grid(
|
||||
sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY
|
||||
)
|
||||
nb.OptionMenu(
|
||||
conf_frame,
|
||||
coriolis_config.override_textvar,
|
||||
coriolis_config.override_textvar.get(),
|
||||
_('Normal'), # LANG: 'Normal' label for Coriolis site override selection
|
||||
_('Beta'), # LANG: 'Beta' label for Coriolis site override selection
|
||||
_('Auto') # LANG: 'Auto' label for Coriolis site override selection
|
||||
tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection
|
||||
tr.tl('Beta'), # LANG: 'Beta' label for Coriolis site override selection
|
||||
tr.tl('Auto') # LANG: 'Auto' label for Coriolis site override selection
|
||||
).grid(sticky=tk.W, row=cur_row, column=1, padx=PADX, pady=BOXY)
|
||||
cur_row += 1
|
||||
|
||||
@ -159,9 +155,9 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None:
|
||||
|
||||
# Convert to unlocalised names
|
||||
coriolis_config.override_mode = {
|
||||
_('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal
|
||||
_('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta
|
||||
_('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto
|
||||
tr.tl('Normal'): 'normal', # LANG: Coriolis normal/beta selection - normal
|
||||
tr.tl('Beta'): 'beta', # LANG: Coriolis normal/beta selection - beta
|
||||
tr.tl('Auto'): 'auto', # LANG: Coriolis normal/beta selection - auto
|
||||
}.get(coriolis_config.override_mode, coriolis_config.override_mode)
|
||||
|
||||
# Check if the language was changed and the override_mode was valid before the change
|
||||
@ -175,18 +171,19 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None:
|
||||
if coriolis_config.override_mode in ('beta', 'normal', 'auto'):
|
||||
coriolis_config.override_textvar.set(
|
||||
value={
|
||||
'auto': _('Auto'), # LANG: 'Auto' label for Coriolis site override selection
|
||||
'normal': _('Normal'), # LANG: 'Normal' label for Coriolis site override selection
|
||||
'beta': _('Beta') # LANG: 'Beta' label for Coriolis site override selection
|
||||
'auto': tr.tl('Auto'), # LANG: 'Auto' label for Coriolis site override selection
|
||||
'normal': tr.tl('Normal'), # LANG: 'Normal' label for Coriolis site override selection
|
||||
'beta': tr.tl('Beta') # LANG: 'Beta' label for Coriolis site override selection
|
||||
# LANG: 'Auto' label for Coriolis site override selection
|
||||
}.get(coriolis_config.override_mode, _('Auto'))
|
||||
}.get(coriolis_config.override_mode, tr.tl('Auto'))
|
||||
)
|
||||
|
||||
# If the override mode is still invalid, default to auto
|
||||
if coriolis_config.override_mode not in ('beta', 'normal', 'auto'):
|
||||
logger.warning(f'Unexpected value {coriolis_config.override_mode=!r}. Defaulting to "auto"')
|
||||
coriolis_config.override_mode = 'auto'
|
||||
coriolis_config.override_textvar.set(value=_('Auto')) # LANG: 'Auto' label for Coriolis site override selection
|
||||
# LANG: 'Auto' label for Coriolis site override selection
|
||||
coriolis_config.override_textvar.set(value=tr.tl('Auto'))
|
||||
|
||||
config.set('coriolis_normal_url', coriolis_config.normal_url)
|
||||
config.set('coriolis_beta_url', coriolis_config.beta_url)
|
||||
@ -196,7 +193,7 @@ def prefs_changed(cmdr: str | None, is_beta: bool) -> None:
|
||||
def _get_target_url(is_beta: bool) -> str:
|
||||
if coriolis_config.override_mode not in ('auto', 'normal', 'beta'):
|
||||
# LANG: Settings>Coriolis - invalid override mode found
|
||||
show_error(_('Invalid Coriolis override mode!'))
|
||||
show_error(tr.tl('Invalid Coriolis override mode!'))
|
||||
logger.warning(f'Unexpected override mode {coriolis_config.override_mode!r}! defaulting to auto!')
|
||||
coriolis_config.override_mode = 'auto'
|
||||
if coriolis_config.override_mode == 'beta':
|
||||
|
@ -31,13 +31,7 @@ import tkinter as tk
|
||||
from platform import system
|
||||
from textwrap import dedent
|
||||
from threading import Lock
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Iterator,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
)
|
||||
from typing import Any, Iterator, Mapping, MutableMapping
|
||||
import requests
|
||||
import companion
|
||||
import edmc_data
|
||||
@ -52,10 +46,7 @@ from myNotebook import Frame
|
||||
from prefs import prefsVersion
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
from util import text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str:
|
||||
return x
|
||||
from l10n import translations as tr
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
@ -441,7 +432,7 @@ class EDDNSender:
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.debug('Failed sending', exc_info=e)
|
||||
# LANG: Error while trying to send data to EDDN
|
||||
self.set_ui_status(_("Error: Can't connect to EDDN"))
|
||||
self.set_ui_status(tr.tl("Error: Can't connect to EDDN"))
|
||||
|
||||
except Exception as e:
|
||||
logger.debug('Failed sending', exc_info=e)
|
||||
@ -566,17 +557,17 @@ class EDDNSender:
|
||||
if status_code == 429: # HTTP UPGRADE REQUIRED
|
||||
logger.warning('EDMC is sending schemas that are too old')
|
||||
# LANG: EDDN has banned this version of our client
|
||||
return _('EDDN Error: EDMC is too old for EDDN. Please update.')
|
||||
return tr.tl('EDDN Error: EDMC is too old for EDDN. Please update.')
|
||||
|
||||
if status_code == 400:
|
||||
# we a validation check or something else.
|
||||
logger.warning(f'EDDN Error: {status_code} -- {exception.response}')
|
||||
# LANG: EDDN returned an error that indicates something about what we sent it was wrong
|
||||
return _('EDDN Error: Validation Failed (EDMC Too Old?). See Log')
|
||||
return tr.tl('EDDN Error: Validation Failed (EDMC Too Old?). See Log')
|
||||
|
||||
logger.warning(f'Unknown status code from EDDN: {status_code} -- {exception.response}')
|
||||
# LANG: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number
|
||||
return _('EDDN Error: Returned {STATUS} status code').format(STATUS=status_code)
|
||||
return tr.tl('EDDN Error: Returned {STATUS} status code').format(STATUS=status_code)
|
||||
|
||||
|
||||
# TODO: a good few of these methods are static or could be classmethods. they should be created as such.
|
||||
@ -2189,7 +2180,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame:
|
||||
this.eddn_station_button = nb.Checkbutton(
|
||||
eddnframe,
|
||||
# LANG: Enable EDDN support for station data checkbox label
|
||||
text=_('Send station data to the Elite Dangerous Data Network'),
|
||||
text=tr.tl('Send station data to the Elite Dangerous Data Network'),
|
||||
variable=this.eddn_station,
|
||||
command=prefsvarchanged
|
||||
) # Output setting
|
||||
@ -2201,7 +2192,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame:
|
||||
this.eddn_system_button = nb.Checkbutton(
|
||||
eddnframe,
|
||||
# LANG: Enable EDDN support for system and other scan data checkbox label
|
||||
text=_('Send system and scan data to the Elite Dangerous Data Network'),
|
||||
text=tr.tl('Send system and scan data to the Elite Dangerous Data Network'),
|
||||
variable=this.eddn_system,
|
||||
command=prefsvarchanged
|
||||
)
|
||||
@ -2213,7 +2204,7 @@ def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame:
|
||||
this.eddn_delay_button = nb.Checkbutton(
|
||||
eddnframe,
|
||||
# LANG: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this
|
||||
text=_('Delay sending until docked'),
|
||||
text=tr.tl('Delay sending until docked'),
|
||||
variable=this.eddn_delay
|
||||
)
|
||||
this.eddn_delay_button.grid(row=cur_row, padx=BUTTONX, pady=PADY, sticky=tk.W)
|
||||
@ -2328,7 +2319,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
"""
|
||||
should_return, new_data = killswitch.check_killswitch('plugins.eddn.journal', entry)
|
||||
if should_return:
|
||||
plug.show_error(_('EDDN journal handler disabled. See Log.')) # LANG: Killswitch disabled EDDN
|
||||
plug.show_error(tr.tl('EDDN journal handler disabled. See Log.')) # LANG: Killswitch disabled EDDN
|
||||
return None
|
||||
|
||||
should_return, new_data = killswitch.check_killswitch(f'plugins.eddn.journal.event.{entry["event"]}', new_data)
|
||||
@ -2530,7 +2521,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.debug('Failed in send_message', exc_info=e)
|
||||
return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN
|
||||
return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN
|
||||
|
||||
except Exception as e:
|
||||
logger.debug('Failed in export_journal_generic', exc_info=e)
|
||||
@ -2568,7 +2559,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.debug(f'Failed exporting {entry["event"]}', exc_info=e)
|
||||
return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN
|
||||
return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f'Failed exporting {entry["event"]}', exc_info=e)
|
||||
@ -2622,7 +2613,8 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001
|
||||
status = this.parent.nametowidget(f".{appname.lower()}.status")
|
||||
old_status = status['text']
|
||||
if not old_status:
|
||||
status['text'] = _('Sending data to EDDN...') # LANG: Status text shown while attempting to send data
|
||||
# LANG: Status text shown while attempting to send data
|
||||
status['text'] = tr.tl('Sending data to EDDN...')
|
||||
status.update_idletasks()
|
||||
|
||||
this.eddn.export_commodities(data, is_beta)
|
||||
@ -2634,7 +2626,7 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.debug('Failed exporting data', exc_info=e)
|
||||
return _("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN
|
||||
return tr.tl("Error: Can't connect to EDDN") # LANG: Error while trying to send data to EDDN
|
||||
|
||||
except Exception as e:
|
||||
logger.debug('Failed exporting data', exc_info=e)
|
||||
|
@ -28,7 +28,7 @@ from queue import Queue
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Any, Literal, Mapping, MutableMapping, cast, Sequence
|
||||
from typing import Any, Literal, Mapping, MutableMapping, cast, Sequence
|
||||
import requests
|
||||
import killswitch
|
||||
import monitor
|
||||
@ -39,10 +39,8 @@ from config import applongname, appname, appversion, config, debug_senders, user
|
||||
from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT
|
||||
from EDMCLogging import get_main_logger
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
from l10n import translations as tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str:
|
||||
return x
|
||||
|
||||
# TODO:
|
||||
# 1) Re-factor EDSM API calls out of journal_entry() into own function.
|
||||
@ -313,7 +311,8 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
|
||||
this.log = tk.IntVar(value=config.get_int('edsm_out') and 1)
|
||||
this.log_button = nb.Checkbutton(
|
||||
frame,
|
||||
text=_('Send flight log and CMDR status to EDSM'), # LANG: Settings>EDSM - Label on checkbox for 'send data'
|
||||
# LANG: Settings>EDSM - Label on checkbox for 'send data'
|
||||
text=tr.tl('Send flight log and CMDR status to EDSM'),
|
||||
variable=this.log,
|
||||
command=prefsvarchanged
|
||||
)
|
||||
@ -328,7 +327,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
|
||||
|
||||
this.label = HyperlinkLabel(
|
||||
frame,
|
||||
text=_('Elite Dangerous Star Map credentials'), # LANG: Elite Dangerous Star Map credentials
|
||||
text=tr.tl('Elite Dangerous Star Map credentials'), # LANG: Elite Dangerous Star Map credentials
|
||||
background=nb.Label().cget('background'),
|
||||
url='https://www.edsm.net/settings/api',
|
||||
underline=True
|
||||
@ -336,21 +335,21 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
|
||||
if this.label:
|
||||
this.label.grid(row=cur_row, columnspan=2, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
cur_row += 1
|
||||
this.cmdr_label = nb.Label(frame, text=_('Cmdr')) # LANG: Game Commander name label in EDSM settings
|
||||
this.cmdr_label = nb.Label(frame, text=tr.tl('Cmdr')) # LANG: Game Commander name label in EDSM settings
|
||||
this.cmdr_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
this.cmdr_text = nb.Label(frame)
|
||||
this.cmdr_text.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.W)
|
||||
|
||||
cur_row += 1
|
||||
# LANG: EDSM Commander name label in EDSM settings
|
||||
this.user_label = nb.Label(frame, text=_('Commander Name'))
|
||||
this.user_label = nb.Label(frame, text=tr.tl('Commander Name'))
|
||||
this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
this.user = nb.EntryMenu(frame)
|
||||
this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
|
||||
cur_row += 1
|
||||
# LANG: EDSM API key label
|
||||
this.apikey_label = nb.Label(frame, text=_('API Key'))
|
||||
this.apikey_label = nb.Label(frame, text=tr.tl('API Key'))
|
||||
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
this.apikey = nb.EntryMenu(frame, show="*", width=50)
|
||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
@ -362,7 +361,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
|
||||
|
||||
show_password_checkbox = nb.Checkbutton(
|
||||
frame,
|
||||
text=_('Show API Key'), # LANG: Text EDSM Show API Key
|
||||
text=tr.tl('Show API Key'), # LANG: Text EDSM Show API Key
|
||||
variable=show_password_var,
|
||||
command=toggle_password_visibility
|
||||
)
|
||||
@ -398,7 +397,7 @@ def prefs_cmdr_changed(cmdr: str | None, is_beta: bool) -> None: # noqa: CCR001
|
||||
else:
|
||||
if this.cmdr_text:
|
||||
# LANG: We have no data on the current commander
|
||||
this.cmdr_text['text'] = _('None')
|
||||
this.cmdr_text['text'] = tr.tl('None')
|
||||
|
||||
to_set: Literal['normal'] | Literal['disabled'] = tk.DISABLED
|
||||
if cmdr and not is_beta and this.log and this.log.get():
|
||||
@ -519,7 +518,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
should_return, new_entry = killswitch.check_killswitch('plugins.edsm.journal', entry, logger)
|
||||
if should_return:
|
||||
# LANG: EDSM plugin - Journal handling disabled by killswitch
|
||||
plug.show_error(_('EDSM Handler disabled. See Log.'))
|
||||
plug.show_error(tr.tl('EDSM Handler disabled. See Log.'))
|
||||
return ''
|
||||
|
||||
should_return, new_entry = killswitch.check_killswitch(
|
||||
@ -606,7 +605,7 @@ entry: {entry!r}'''
|
||||
# LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data
|
||||
logger.info("EDSM only accepts Live galaxy data")
|
||||
this.legacy_galaxy_last_notified = datetime.now(timezone.utc)
|
||||
return _("EDSM only accepts Live galaxy data") # LANG: EDSM - Only Live data
|
||||
return tr.tl("EDSM only accepts Live galaxy data") # LANG: EDSM - Only Live data
|
||||
|
||||
return ''
|
||||
|
||||
@ -778,7 +777,7 @@ def send_to_edsm( # noqa: CCR001
|
||||
if msg_num // 100 == 2:
|
||||
logger.warning(f'EDSM\t{msg_num} {msg}\t{json.dumps(pending, separators=(",", ": "))}')
|
||||
# LANG: EDSM Plugin - Error message from EDSM API
|
||||
plug.show_error(_('Error: EDSM {MSG}').format(MSG=msg))
|
||||
plug.show_error(tr.tl('Error: EDSM {MSG}').format(MSG=msg))
|
||||
else:
|
||||
if msg_num // 100 == 1:
|
||||
logger.trace_if('plugin.edsm.api', 'Overall OK')
|
||||
@ -944,7 +943,7 @@ def worker() -> None: # noqa: CCR001 C901
|
||||
|
||||
else:
|
||||
# LANG: EDSM Plugin - Error connecting to EDSM API
|
||||
plug.show_error(_("Error: Can't connect to EDSM"))
|
||||
plug.show_error(tr.tl("Error: Can't connect to EDSM"))
|
||||
if entry['event'].lower() in ('shutdown', 'commander', 'fileheader'):
|
||||
# Game shutdown or new login, so we MUST not hang on to pending
|
||||
pending = []
|
||||
@ -1018,11 +1017,11 @@ def edsm_notify_system(reply: Mapping[str, Any]) -> None:
|
||||
if not reply:
|
||||
this.system_link['image'] = this._IMG_ERROR
|
||||
# LANG: EDSM Plugin - Error connecting to EDSM API
|
||||
plug.show_error(_("Error: Can't connect to EDSM"))
|
||||
plug.show_error(tr.tl("Error: Can't connect to EDSM"))
|
||||
elif reply['msgnum'] // 100 not in (1, 4):
|
||||
this.system_link['image'] = this._IMG_ERROR
|
||||
# LANG: EDSM Plugin - Error message from EDSM API
|
||||
plug.show_error(_('Error: EDSM {MSG}').format(MSG=reply['msg']))
|
||||
plug.show_error(tr.tl('Error: EDSM {MSG}').format(MSG=reply['msg']))
|
||||
elif reply.get('systemCreated'):
|
||||
this.system_link['image'] = this._IMG_NEW
|
||||
else:
|
||||
|
@ -30,7 +30,7 @@ from datetime import datetime, timedelta, timezone
|
||||
from operator import itemgetter
|
||||
from threading import Lock, Thread
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union
|
||||
from typing import Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union
|
||||
import requests
|
||||
import edmc_data
|
||||
import killswitch
|
||||
@ -42,13 +42,10 @@ from config import applongname, appname, appversion, config, debug_senders
|
||||
from EDMCLogging import get_main_logger
|
||||
from monitor import monitor
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
from l10n import translations as tr
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str:
|
||||
return x
|
||||
|
||||
|
||||
_TIMEOUT = 20
|
||||
FAKE = ('CQC', 'Training', 'Destination') # Fake systems that shouldn't be sent to Inara
|
||||
@ -264,7 +261,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
|
||||
this.log = tk.IntVar(value=config.get_int('inara_out') and 1)
|
||||
this.log_button = nb.Checkbutton(
|
||||
frame,
|
||||
text=_('Send flight log and Cmdr status to Inara'), # LANG: Checkbox to enable INARA API Usage
|
||||
text=tr.tl('Send flight log and Cmdr status to Inara'), # LANG: Checkbox to enable INARA API Usage
|
||||
variable=this.log,
|
||||
command=prefsvarchanged
|
||||
)
|
||||
@ -280,7 +277,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
|
||||
# Section heading in settings
|
||||
this.label = HyperlinkLabel(
|
||||
frame,
|
||||
text=_('Inara credentials'), # LANG: Text for INARA API keys link ( goes to https://inara.cz/settings-api )
|
||||
text=tr.tl('Inara credentials'), # LANG: Text for INARA API keys link ( goes to https://inara.cz/settings-api )
|
||||
background=nb.Label().cget('background'),
|
||||
url='https://inara.cz/settings-api',
|
||||
underline=True
|
||||
@ -290,7 +287,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
|
||||
cur_row += 1
|
||||
|
||||
# LANG: Inara API key label
|
||||
this.apikey_label = nb.Label(frame, text=_('API Key')) # Inara setting
|
||||
this.apikey_label = nb.Label(frame, text=tr.tl('API Key')) # Inara setting
|
||||
this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
|
||||
this.apikey = nb.EntryMenu(frame, show="*", width=50)
|
||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
@ -301,7 +298,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
|
||||
show_password_var.set(False) # Password is initially masked
|
||||
show_password_checkbox = nb.Checkbutton(
|
||||
frame,
|
||||
text=_('Show API Key'), # LANG: Text Inara Show API key
|
||||
text=tr.tl('Show API Key'), # LANG: Text Inara Show API key
|
||||
variable=show_password_var,
|
||||
command=toggle_password_visibility,
|
||||
)
|
||||
@ -407,7 +404,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger)
|
||||
if should_return:
|
||||
plug.show_error(_('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch
|
||||
plug.show_error(tr.tl('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch
|
||||
logger.trace('returning due to killswitch match')
|
||||
return ''
|
||||
|
||||
@ -432,9 +429,9 @@ def journal_entry( # noqa: C901, CCR001
|
||||
and config.get_int('inara_out') and not (is_beta or this.multicrew or credentials(cmdr))
|
||||
):
|
||||
# LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data
|
||||
logger.info(_("Inara only accepts Live galaxy data"))
|
||||
logger.info(tr.tl("Inara only accepts Live galaxy data"))
|
||||
this.legacy_galaxy_last_notified = datetime.now(timezone.utc)
|
||||
return _("Inara only accepts Live galaxy data") # LANG: Inara - Only Live data
|
||||
return tr.tl("Inara only accepts Live galaxy data") # LANG: Inara - Only Live data
|
||||
|
||||
return ''
|
||||
|
||||
@ -553,23 +550,6 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
# Ship change
|
||||
if event_name == 'Loadout' and this.shipswap:
|
||||
cur_ship = {
|
||||
'shipType': state['ShipType'],
|
||||
'shipGameID': state['ShipID'],
|
||||
'shipName': state['ShipName'], # Can be None
|
||||
'shipIdent': state['ShipIdent'], # Can be None
|
||||
'isCurrentShip': True,
|
||||
}
|
||||
|
||||
if state['HullValue']:
|
||||
cur_ship['shipHullValue'] = state['HullValue']
|
||||
|
||||
if state['ModulesValue']:
|
||||
cur_ship['shipModulesValue'] = state['ModulesValue']
|
||||
|
||||
cur_ship['shipRebuyCost'] = state['Rebuy']
|
||||
new_add_event('setCommanderShip', entry['timestamp'], cur_ship)
|
||||
|
||||
this.loadout = make_loadout(state)
|
||||
new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
|
||||
this.shipswap = False
|
||||
@ -857,7 +837,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
for ship in this.fleet:
|
||||
new_add_event('setCommanderShip', entry['timestamp'], ship)
|
||||
# Loadout
|
||||
if event_name == 'Loadout' and not this.newsession:
|
||||
if event_name == 'Loadout':
|
||||
loadout = make_loadout(state)
|
||||
if this.loadout != loadout:
|
||||
this.loadout = loadout
|
||||
@ -871,6 +851,26 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
|
||||
|
||||
cur_ship = {
|
||||
'shipType': state['ShipType'],
|
||||
'shipGameID': state['ShipID'],
|
||||
'shipName': state['ShipName'], # Can be None
|
||||
'shipIdent': state['ShipIdent'], # Can be None
|
||||
'isCurrentShip': True,
|
||||
'shipMaxJumpRange': entry['MaxJumpRange'],
|
||||
'shipCargoCapacity': entry['CargoCapacity']
|
||||
}
|
||||
if state['HullValue']:
|
||||
cur_ship['shipHullValue'] = state['HullValue']
|
||||
|
||||
if state['ModulesValue']:
|
||||
cur_ship['shipModulesValue'] = state['ModulesValue']
|
||||
|
||||
if state['Rebuy']:
|
||||
cur_ship['shipRebuyCost'] = state['Rebuy']
|
||||
|
||||
new_add_event('setCommanderShip', entry['timestamp'], cur_ship)
|
||||
|
||||
# Stored modules
|
||||
if event_name == 'StoredModules':
|
||||
items = {mod['StorageSlot']: mod for mod in entry['Items']} # Impose an order
|
||||
@ -1642,7 +1642,7 @@ def handle_api_error(data: Mapping[str, Any], status: int, reply: dict[str, Any]
|
||||
logger.warning(f'Inara\t{status} {error_message}')
|
||||
logger.debug(f'JSON data:\n{json.dumps(data, indent=2, separators = (",", ": "))}')
|
||||
# LANG: INARA API returned some kind of error (error message will be contained in {MSG})
|
||||
plug.show_error(_('Error: Inara {MSG}').format(MSG=error_message))
|
||||
plug.show_error(tr.tl('Error: Inara {MSG}').format(MSG=error_message))
|
||||
|
||||
|
||||
def handle_success_reply(data: Mapping[str, Any], reply: dict[str, Any]) -> None:
|
||||
@ -1675,7 +1675,7 @@ def handle_individual_error(data_event: dict[str, Any], reply_status: int, reply
|
||||
|
||||
if reply_status // 100 != 2:
|
||||
# LANG: INARA API returned some kind of error (error message will be contained in {MSG})
|
||||
plug.show_error(_('Error: Inara {MSG}').format(
|
||||
plug.show_error(tr.tl('Error: Inara {MSG}').format(
|
||||
MSG=f'{data_event["eventName"]}, {reply_text}'
|
||||
))
|
||||
|
||||
|
161
prefs.py
161
prefs.py
@ -8,29 +8,24 @@ import pathlib
|
||||
import sys
|
||||
import tempfile
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from os import system
|
||||
from os.path import expanduser, expandvars, join, normpath
|
||||
from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812
|
||||
from tkinter import ttk
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional, Type
|
||||
|
||||
from typing import Any, Callable, Optional, Type
|
||||
import myNotebook as nb # noqa: N813
|
||||
import plug
|
||||
from config import appversion_nobuild, config
|
||||
from EDMCLogging import edmclogger, get_main_logger
|
||||
from constants import appname
|
||||
from hotkey import hotkeymgr
|
||||
from l10n import Translations
|
||||
from l10n import translations as tr
|
||||
from monitor import monitor
|
||||
from theme import theme
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
logger = get_main_logger()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str:
|
||||
return x
|
||||
|
||||
# TODO: Decouple this from platform as far as possible
|
||||
|
||||
@ -44,14 +39,21 @@ if TYPE_CHECKING:
|
||||
|
||||
def help_open_log_folder() -> None:
|
||||
"""Open the folder logs are stored in."""
|
||||
logfile_loc = pathlib.Path(tempfile.gettempdir())
|
||||
logfile_loc /= f'{appname}'
|
||||
logger.warning(
|
||||
DeprecationWarning("This function is deprecated, use open_log_folder instead. "
|
||||
"This function will be removed in 6.0 or later")
|
||||
)
|
||||
open_folder(pathlib.Path(tempfile.gettempdir()) / appname)
|
||||
|
||||
|
||||
def open_folder(file: pathlib.Path) -> None:
|
||||
"""Open the given file in the OS file explorer."""
|
||||
if sys.platform.startswith('win'):
|
||||
# On Windows, use the "start" command to open the folder
|
||||
system(f'start "" "{logfile_loc}"')
|
||||
system(f'start "" "{file}"')
|
||||
elif sys.platform.startswith('linux'):
|
||||
# On Linux, use the "xdg-open" command to open the folder
|
||||
system(f'xdg-open "{logfile_loc}"')
|
||||
system(f'xdg-open "{file}"')
|
||||
|
||||
|
||||
class PrefsVersion:
|
||||
@ -224,7 +226,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.parent = parent
|
||||
self.callback = callback
|
||||
# LANG: File > Settings (macOS)
|
||||
self.title(_('Settings'))
|
||||
self.title(tr.tl('Settings'))
|
||||
|
||||
if parent.winfo_viewable():
|
||||
self.transient(parent)
|
||||
@ -270,7 +272,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
buttonframe.columnconfigure(0, weight=1)
|
||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
||||
# LANG: 'OK' button on Settings/Preferences window
|
||||
button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
|
||||
button = ttk.Button(buttonframe, text=tr.tl('OK'), command=self.apply)
|
||||
button.grid(row=0, column=1, sticky=tk.E)
|
||||
button.bind("<Return>", lambda event: self.apply())
|
||||
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
||||
@ -300,6 +302,9 @@ class PreferencesDialog(tk.Toplevel):
|
||||
):
|
||||
self.geometry(f"+{position.left}+{position.top}")
|
||||
|
||||
# Set Log Directory
|
||||
self.logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
|
||||
|
||||
def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None:
|
||||
output_frame = nb.Frame(root_notebook)
|
||||
output_frame.columnconfigure(0, weight=1)
|
||||
@ -313,13 +318,13 @@ class PreferencesDialog(tk.Toplevel):
|
||||
row = AutoInc(start=0)
|
||||
|
||||
# LANG: Settings > Output - choosing what data to save to files
|
||||
self.out_label = nb.Label(output_frame, text=_('Please choose what data to save'))
|
||||
self.out_label = nb.Label(output_frame, text=tr.tl('Please choose what data to save'))
|
||||
self.out_label.grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
self.out_csv = tk.IntVar(value=1 if (output & config.OUT_MKT_CSV) else 0)
|
||||
self.out_csv_button = nb.Checkbutton(
|
||||
output_frame,
|
||||
text=_('Market data in CSV format file'), # LANG: Settings > Output option
|
||||
text=tr.tl('Market data in CSV format file'), # LANG: Settings > Output option
|
||||
variable=self.out_csv,
|
||||
command=self.outvarchanged
|
||||
)
|
||||
@ -328,7 +333,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.out_td = tk.IntVar(value=1 if (output & config.OUT_MKT_TD) else 0)
|
||||
self.out_td_button = nb.Checkbutton(
|
||||
output_frame,
|
||||
text=_('Market data in Trade Dangerous format file'), # LANG: Settings > Output option
|
||||
text=tr.tl('Market data in Trade Dangerous format file'), # LANG: Settings > Output option
|
||||
variable=self.out_td,
|
||||
command=self.outvarchanged
|
||||
)
|
||||
@ -338,7 +343,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Output setting
|
||||
self.out_ship_button = nb.Checkbutton(
|
||||
output_frame,
|
||||
text=_('Ship loadout'), # LANG: Settings > Output option
|
||||
text=tr.tl('Ship loadout'), # LANG: Settings > Output option
|
||||
variable=self.out_ship,
|
||||
command=self.outvarchanged
|
||||
)
|
||||
@ -348,7 +353,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Output setting
|
||||
self.out_auto_button = nb.Checkbutton(
|
||||
output_frame,
|
||||
text=_('Automatically update on docking'), # LANG: Settings > Output option
|
||||
text=tr.tl('Automatically update on docking'), # LANG: Settings > Output option
|
||||
variable=self.out_auto,
|
||||
command=self.outvarchanged
|
||||
)
|
||||
@ -357,14 +362,14 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.outdir = tk.StringVar()
|
||||
self.outdir.set(str(config.get_str('outdir')))
|
||||
# LANG: Settings > Output - Label for "where files are located"
|
||||
self.outdir_label = nb.Label(output_frame, text=_('File location')+':') # Section heading in settings
|
||||
self.outdir_label = nb.Label(output_frame, text=tr.tl('File location')+':') # Section heading in settings
|
||||
# Type ignored due to incorrect type annotation. a 2 tuple does padding for each side
|
||||
self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()) # type: ignore
|
||||
|
||||
self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
|
||||
self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
text = _('Browse...') # LANG: NOT-macOS Settings - files location selection button
|
||||
text = tr.tl('Browse...') # LANG: NOT-macOS Settings - files location selection button
|
||||
|
||||
self.outbutton = ttk.Button(
|
||||
output_frame,
|
||||
@ -372,12 +377,12 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Technically this is different from the label in Settings > Output, as *this* is used
|
||||
# as the title of the popup folder selection window.
|
||||
# LANG: Settings > Output - Label for "where files are located"
|
||||
command=lambda: self.filebrowse(_('File location'), self.outdir)
|
||||
command=lambda: self.filebrowse(tr.tl('File location'), self.outdir)
|
||||
)
|
||||
self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get())
|
||||
|
||||
# LANG: Label for 'Output' Settings/Preferences tab
|
||||
root_notebook.add(output_frame, text=_('Output')) # Tab heading in settings
|
||||
root_notebook.add(output_frame, text=tr.tl('Output')) # Tab heading in settings
|
||||
|
||||
def __setup_plugin_tabs(self, notebook: ttk.Notebook) -> None:
|
||||
for plugin in plug.PLUGINS:
|
||||
@ -403,19 +408,19 @@ class PreferencesDialog(tk.Toplevel):
|
||||
nb.Label(
|
||||
config_frame,
|
||||
# LANG: Settings > Configuration - Label for Journal files location
|
||||
text=_('E:D journal file location')+':'
|
||||
text=tr.tl('E:D journal file location')+':'
|
||||
).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
text = _('Browse...') # LANG: NOT-macOS Setting - files location selection button
|
||||
text = tr.tl('Browse...') # LANG: NOT-macOS Setting - files location selection button
|
||||
|
||||
with row as cur_row:
|
||||
self.logbutton = ttk.Button(
|
||||
config_frame,
|
||||
text=text,
|
||||
# LANG: Settings > Configuration - Label for Journal files location
|
||||
command=lambda: self.filebrowse(_('E:D journal file location'), self.logdir)
|
||||
command=lambda: self.filebrowse(tr.tl('E:D journal file location'), self.logdir)
|
||||
)
|
||||
self.logbutton.grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
|
||||
@ -424,7 +429,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
ttk.Button(
|
||||
config_frame,
|
||||
# LANG: Settings > Configuration - Label on 'reset journal files location to default' button
|
||||
text=_('Default'),
|
||||
text=tr.tl('Default'),
|
||||
command=self.logdir_reset,
|
||||
state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED
|
||||
).grid(column=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
@ -438,13 +443,13 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
nb.Label(
|
||||
config_frame,
|
||||
text=_('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section
|
||||
text=tr.tl('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
nb.Checkbutton(
|
||||
config_frame,
|
||||
# LANG: Configuration - Enable or disable the Fleet Carrier CAPI calls
|
||||
text=_('Enable Fleetcarrier CAPI Queries'),
|
||||
text=tr.tl('Enable Fleetcarrier CAPI Queries'),
|
||||
variable=self.capi_fleetcarrier
|
||||
).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
@ -460,7 +465,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
with row as cur_row:
|
||||
nb.Label(
|
||||
config_frame,
|
||||
text=_('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows
|
||||
text=tr.tl('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
||||
self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER)
|
||||
@ -469,7 +474,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# No hotkey/shortcut currently defined
|
||||
# TODO: display Only shows up on windows
|
||||
# LANG: No hotkey/shortcut set
|
||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
|
||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else tr.tl('None')
|
||||
)
|
||||
|
||||
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
|
||||
@ -480,7 +485,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.hotkey_only_btn = nb.Checkbutton(
|
||||
config_frame,
|
||||
# LANG: Configuration - Act on hotkey only when ED is in foreground
|
||||
text=_('Only when Elite: Dangerous is the active app'),
|
||||
text=tr.tl('Only when Elite: Dangerous is the active app'),
|
||||
variable=self.hotkey_only,
|
||||
state=tk.NORMAL if self.hotkey_code else tk.DISABLED
|
||||
)
|
||||
@ -491,7 +496,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.hotkey_play_btn = nb.Checkbutton(
|
||||
config_frame,
|
||||
# LANG: Configuration - play sound when hotkey used
|
||||
text=_('Play sound'),
|
||||
text=tr.tl('Play sound'),
|
||||
variable=self.hotkey_play,
|
||||
state=tk.NORMAL if self.hotkey_code else tk.DISABLED
|
||||
)
|
||||
@ -506,7 +511,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.disable_autoappupdatecheckingame_btn = nb.Checkbutton(
|
||||
config_frame,
|
||||
# LANG: Configuration - disable checks for app updates when in-game
|
||||
text=_('Disable Automatic Application Updates Check when in-game'),
|
||||
text=tr.tl('Disable Automatic Application Updates Check when in-game'),
|
||||
variable=self.disable_autoappupdatecheckingame,
|
||||
command=self.disable_autoappupdatecheckingame_changed
|
||||
)
|
||||
@ -521,7 +526,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
# Settings prompt for preferred ship loadout, system and station info websites
|
||||
# LANG: Label for preferred shipyard, system and station 'providers'
|
||||
nb.Label(config_frame, text=_('Preferred websites')).grid(
|
||||
nb.Label(config_frame, text=tr.tl('Preferred websites')).grid(
|
||||
columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||
)
|
||||
|
||||
@ -532,7 +537,9 @@ class PreferencesDialog(tk.Toplevel):
|
||||
)
|
||||
# Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis
|
||||
# LANG: Label for Shipyard provider selection
|
||||
nb.Label(config_frame, text=_('Shipyard')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
nb.Label(config_frame, text=tr.tl('Shipyard')).grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row
|
||||
)
|
||||
self.shipyard_button = nb.OptionMenu(
|
||||
config_frame, self.shipyard_provider, self.shipyard_provider.get(), *plug.provides('shipyard_url')
|
||||
)
|
||||
@ -544,7 +551,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.alt_shipyard_open_btn = nb.Checkbutton(
|
||||
config_frame,
|
||||
# LANG: Label for checkbox to utilise alternative Coriolis URL method
|
||||
text=_('Use alternate URL method'),
|
||||
text=tr.tl('Use alternate URL method'),
|
||||
variable=self.alt_shipyard_open,
|
||||
command=self.alt_shipyard_open_changed,
|
||||
)
|
||||
@ -558,7 +565,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
)
|
||||
|
||||
# LANG: Configuration - Label for selection of 'System' provider website
|
||||
nb.Label(config_frame, text=_('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
nb.Label(config_frame, text=tr.tl('System')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
self.system_button = nb.OptionMenu(
|
||||
config_frame,
|
||||
self.system_provider,
|
||||
@ -576,7 +583,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
)
|
||||
|
||||
# LANG: Configuration - Label for selection of 'Station' provider website
|
||||
nb.Label(config_frame, text=_('Station')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
nb.Label(config_frame, text=tr.tl('Station')).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
self.station_button = nb.OptionMenu(
|
||||
config_frame,
|
||||
self.station_provider,
|
||||
@ -597,7 +604,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
nb.Label(
|
||||
config_frame,
|
||||
# LANG: Configuration - Label for selection of Log Level
|
||||
text=_('Log Level')
|
||||
text=tr.tl('Log Level')
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
||||
current_loglevel = config.get_str('loglevel')
|
||||
@ -624,15 +631,15 @@ class PreferencesDialog(tk.Toplevel):
|
||||
ttk.Button(
|
||||
config_frame,
|
||||
# LANG: Label on button used to open a filesystem folder
|
||||
text=_('Open Log Folder'), # Button that opens a folder in Explorer/Finder
|
||||
command=lambda: help_open_log_folder()
|
||||
text=tr.tl('Open Log Folder'), # Button that opens a folder in Explorer/Finder
|
||||
command=lambda: open_folder(self.logfile_loc)
|
||||
).grid(column=2, padx=self.PADX, pady=0, sticky=tk.NSEW, row=cur_row)
|
||||
|
||||
# Big spacer
|
||||
nb.Label(config_frame).grid(sticky=tk.W, row=row.get())
|
||||
|
||||
# LANG: Label for 'Configuration' tab in Settings
|
||||
notebook.add(config_frame, text=_('Configuration'))
|
||||
notebook.add(config_frame, text=tr.tl('Configuration'))
|
||||
|
||||
def __setup_privacy_tab(self, notebook: ttk.Notebook) -> None:
|
||||
privacy_frame = nb.Frame(notebook)
|
||||
@ -641,37 +648,37 @@ class PreferencesDialog(tk.Toplevel):
|
||||
row = AutoInc(start=0)
|
||||
|
||||
# LANG: UI elements privacy section header in privacy tab of preferences
|
||||
nb.Label(privacy_frame, text=_('Main UI privacy options')).grid(
|
||||
nb.Label(privacy_frame, text=tr.tl('Main UI privacy options')).grid(
|
||||
row=row.get(), column=0, sticky=tk.W, padx=self.PADX, pady=self.PADY
|
||||
)
|
||||
|
||||
nb.Checkbutton(
|
||||
# LANG: Hide private group owner name from UI checkbox
|
||||
privacy_frame, text=_('Hide private group name in UI'),
|
||||
privacy_frame, text=tr.tl('Hide private group name in UI'),
|
||||
variable=self.hide_private_group
|
||||
).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W)
|
||||
nb.Checkbutton(
|
||||
# LANG: Hide multicrew captain name from main UI checkbox
|
||||
privacy_frame, text=_('Hide multi-crew captain name'),
|
||||
privacy_frame, text=tr.tl('Hide multi-crew captain name'),
|
||||
variable=self.hide_multicrew_captain
|
||||
).grid(row=row.get(), column=0, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W)
|
||||
|
||||
notebook.add(privacy_frame, text=_('Privacy')) # LANG: Preferences privacy tab title
|
||||
notebook.add(privacy_frame, text=tr.tl('Privacy')) # LANG: Preferences privacy tab title
|
||||
|
||||
def __setup_appearance_tab(self, notebook: ttk.Notebook) -> None:
|
||||
self.languages = Translations.available_names()
|
||||
self.languages = tr.available_names()
|
||||
# Appearance theme and language setting
|
||||
# LANG: The system default language choice in Settings > Appearance
|
||||
self.lang = tk.StringVar(value=self.languages.get(config.get_str('language'), _('Default')))
|
||||
self.lang = tk.StringVar(value=self.languages.get(config.get_str('language'), tr.tl('Default')))
|
||||
self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
|
||||
self.minimize_system_tray = tk.BooleanVar(value=config.get_bool('minimize_system_tray'))
|
||||
self.theme = tk.IntVar(value=config.get_int('theme'))
|
||||
self.theme_colors = [config.get_str('dark_text'), config.get_str('dark_highlight')]
|
||||
self.theme_prompts = [
|
||||
# LANG: Label for Settings > Appeareance > selection of 'normal' text colour
|
||||
_('Normal text'), # Dark theme color setting
|
||||
tr.tl('Normal text'), # Dark theme color setting
|
||||
# LANG: Label for Settings > Appeareance > selection of 'highlightes' text colour
|
||||
_('Highlighted text'), # Dark theme color setting
|
||||
tr.tl('Highlighted text'), # Dark theme color setting
|
||||
]
|
||||
|
||||
row = AutoInc(start=0)
|
||||
@ -680,7 +687,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
appearance_frame.columnconfigure(2, weight=1)
|
||||
with row as cur_row:
|
||||
# LANG: Appearance - Label for selection of application display language
|
||||
nb.Label(appearance_frame, text=_('Language')).grid(
|
||||
nb.Label(appearance_frame, text=tr.tl('Language')).grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row
|
||||
)
|
||||
self.lang_button = nb.OptionMenu(appearance_frame, self.lang, self.lang.get(), *self.languages.values())
|
||||
@ -692,28 +699,29 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
# Appearance setting
|
||||
# LANG: Label for Settings > Appearance > Theme selection
|
||||
nb.Label(appearance_frame, text=_('Theme')).grid(
|
||||
nb.Label(appearance_frame, text=tr.tl('Theme')).grid(
|
||||
columnspan=3, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||
)
|
||||
|
||||
# Appearance theme and language setting
|
||||
nb.Radiobutton(
|
||||
# LANG: Label for 'Default' theme radio button
|
||||
appearance_frame, text=_('Default'), variable=self.theme,
|
||||
appearance_frame, text=tr.tl('Default'), variable=self.theme,
|
||||
value=theme.THEME_DEFAULT, command=self.themevarchanged
|
||||
).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
# Appearance theme setting
|
||||
nb.Radiobutton(
|
||||
# LANG: Label for 'Dark' theme radio button
|
||||
appearance_frame, text=_('Dark'), variable=self.theme, value=theme.THEME_DARK, command=self.themevarchanged
|
||||
appearance_frame, text=tr.tl('Dark'), variable=self.theme,
|
||||
value=theme.THEME_DARK, command=self.themevarchanged
|
||||
).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
if sys.platform == 'win32':
|
||||
nb.Radiobutton(
|
||||
appearance_frame,
|
||||
# LANG: Label for 'Transparent' theme radio button
|
||||
text=_('Transparent'), # Appearance theme setting
|
||||
text=tr.tl('Transparent'), # Appearance theme setting
|
||||
variable=self.theme,
|
||||
value=theme.THEME_TRANSPARENT,
|
||||
command=self.themevarchanged
|
||||
@ -727,7 +735,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.theme_button_0 = tk.Button(
|
||||
appearance_frame,
|
||||
# LANG: Appearance - Example 'Normal' text
|
||||
text=_('Station'),
|
||||
text=tr.tl('Station'),
|
||||
background='grey4',
|
||||
command=lambda: self.themecolorbrowse(0)
|
||||
)
|
||||
@ -759,7 +767,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
)
|
||||
with row as cur_row:
|
||||
# LANG: Appearance - Label for selection of UI scaling
|
||||
nb.Label(appearance_frame, text=_('UI Scale Percentage')).grid(
|
||||
nb.Label(appearance_frame, text=tr.tl('UI Scale Percentage')).grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row
|
||||
)
|
||||
|
||||
@ -780,7 +788,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.ui_scaling_defaultis = nb.Label(
|
||||
appearance_frame,
|
||||
# LANG: Appearance - Help/hint text for UI scaling selection
|
||||
text=_('100 means Default{CR}Restart Required for{CR}changes to take effect!')
|
||||
text=tr.tl('100 means Default{CR}Restart Required for{CR}changes to take effect!')
|
||||
).grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.E, row=cur_row)
|
||||
|
||||
# Transparency slider
|
||||
@ -790,7 +798,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
with row as cur_row:
|
||||
# LANG: Appearance - Label for selection of main window transparency
|
||||
nb.Label(appearance_frame, text=_("Main window transparency")).grid(
|
||||
nb.Label(appearance_frame, text=tr.tl("Main window transparency")).grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row
|
||||
)
|
||||
self.transparency = tk.IntVar()
|
||||
@ -810,7 +818,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
nb.Label(
|
||||
appearance_frame,
|
||||
# LANG: Appearance - Help/hint text for Main window transparency selection
|
||||
text=_(
|
||||
text=tr.tl(
|
||||
"100 means fully opaque.{CR}"
|
||||
"Window is updated in real time"
|
||||
).format(CR='\n')
|
||||
@ -832,7 +840,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.ontop_button = nb.Checkbutton(
|
||||
appearance_frame,
|
||||
# LANG: Appearance - Label for checkbox to select if application always on top
|
||||
text=_('Always on top'),
|
||||
text=tr.tl('Always on top'),
|
||||
variable=self.always_ontop,
|
||||
command=self.themevarchanged
|
||||
)
|
||||
@ -844,7 +852,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
nb.Checkbutton(
|
||||
appearance_frame,
|
||||
# LANG: Appearance option for Windows "minimize to system tray"
|
||||
text=_('Minimize to system tray'),
|
||||
text=tr.tl('Minimize to system tray'),
|
||||
variable=self.minimize_system_tray,
|
||||
command=self.themevarchanged
|
||||
).grid(columnspan=3, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get()) # Appearance setting
|
||||
@ -852,7 +860,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
nb.Label(appearance_frame).grid(sticky=tk.W) # big spacer
|
||||
|
||||
# LANG: Label for Settings > Appearance tab
|
||||
notebook.add(appearance_frame, text=_('Appearance')) # Tab heading in settings
|
||||
notebook.add(appearance_frame, text=tr.tl('Appearance')) # Tab heading in settings
|
||||
|
||||
def __setup_plugin_tab(self, notebook: ttk.Notebook) -> None: # noqa: CCR001
|
||||
# Plugin settings and info
|
||||
@ -864,7 +872,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
# Section heading in settings
|
||||
# LANG: Label for location of third-party plugins folder
|
||||
nb.Label(plugins_frame, text=_('Plugins folder') + ':').grid(
|
||||
nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||
)
|
||||
|
||||
@ -877,14 +885,15 @@ class PreferencesDialog(tk.Toplevel):
|
||||
plugins_frame,
|
||||
# Help text in settings
|
||||
# LANG: Tip/label about how to disable plugins
|
||||
text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
||||
text=tr.tl(
|
||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
||||
).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||
|
||||
ttk.Button(
|
||||
plugins_frame,
|
||||
# LANG: Label on button used to open a filesystem folder
|
||||
text=_('Open'), # Button that opens a folder in Explorer/Finder
|
||||
command=lambda: webbrowser.open(f'file:///{config.plugin_dir_path}')
|
||||
text=tr.tl('Open'), # Button that opens a folder in Explorer/Finder
|
||||
command=lambda: open_folder(config.plugin_dir_path)
|
||||
).grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.N, row=cur_row)
|
||||
|
||||
enabled_plugins = list(filter(lambda x: x.folder and x.module, plug.PLUGINS))
|
||||
@ -895,7 +904,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
nb.Label(
|
||||
plugins_frame,
|
||||
# LANG: Label on list of enabled plugins
|
||||
text=_('Enabled Plugins')+':' # List of plugins in settings
|
||||
text=tr.tl('Enabled Plugins')+':' # List of plugins in settings
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
for plugin in enabled_plugins:
|
||||
@ -915,13 +924,13 @@ class PreferencesDialog(tk.Toplevel):
|
||||
columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
# LANG: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x
|
||||
nb.Label(plugins_frame, text=_('Plugins Without Python 3.x Support')+':').grid(
|
||||
nb.Label(plugins_frame, text=tr.tl('Plugins Without Python 3.x Support')+':').grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||
)
|
||||
|
||||
HyperlinkLabel(
|
||||
# LANG: Plugins - Label on URL to documentation about migrating plugins from Python 2.7
|
||||
plugins_frame, text=_('Information on migrating plugins'),
|
||||
plugins_frame, text=tr.tl('Information on migrating plugins'),
|
||||
background=nb.Label().cget('background'),
|
||||
url='https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#migration-from-python-27',
|
||||
underline=True
|
||||
@ -943,7 +952,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
nb.Label(
|
||||
plugins_frame,
|
||||
# LANG: Label on list of user-disabled plugins
|
||||
text=_('Disabled Plugins')+':' # List of plugins in settings
|
||||
text=tr.tl('Disabled Plugins')+':' # List of plugins in settings
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
for plugin in disabled_plugins:
|
||||
@ -958,7 +967,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
# LANG: Plugins - Label for list of 'broken' plugins that failed to load
|
||||
nb.Label(plugins_frame, text=_('Broken Plugins')+':').grid(
|
||||
nb.Label(plugins_frame, text=tr.tl('Broken Plugins')+':').grid(
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
||||
)
|
||||
|
||||
@ -969,7 +978,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
)
|
||||
|
||||
# LANG: Label on Settings > Plugins tab
|
||||
notebook.add(plugins_frame, text=_('Plugins')) # Tab heading in settings
|
||||
notebook.add(plugins_frame, text=tr.tl('Plugins')) # Tab heading in settings
|
||||
|
||||
def cmdrchanged(self, event=None):
|
||||
"""
|
||||
@ -1122,7 +1131,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.hotkey_text.insert(
|
||||
0,
|
||||
# LANG: No hotkey/shortcut set
|
||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None'))
|
||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else tr.tl('None'))
|
||||
|
||||
def hotkeylisten(self, event: 'tk.Event[Any]') -> str:
|
||||
"""
|
||||
@ -1155,7 +1164,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
else:
|
||||
# LANG: No hotkey/shortcut set
|
||||
event.widget.insert(0, _('None'))
|
||||
event.widget.insert(0, tr.tl('None'))
|
||||
self.hotkey_only_btn['state'] = tk.DISABLED
|
||||
self.hotkey_play_btn['state'] = tk.DISABLED
|
||||
|
||||
@ -1205,7 +1214,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
lang_codes = {v: k for k, v in self.languages.items()} # Codes by name
|
||||
config.set('language', lang_codes.get(self.lang.get()) or '') # or '' used here due to Default being None above
|
||||
Translations.install(config.get_str('language', default=None)) # type: ignore # This sets self in weird ways.
|
||||
tr.install(config.get_str('language', default=None)) # type: ignore # This sets self in weird ways.
|
||||
|
||||
# Privacy options
|
||||
config.set('hide_private_group', self.hide_private_group.get())
|
||||
|
@ -13,14 +13,14 @@ flake8-annotations-coverage==0.0.6
|
||||
flake8-cognitive-complexity==0.1.0
|
||||
flake8-comprehensions==3.14.0
|
||||
flake8-docstrings==1.7.0
|
||||
flake8-json==23.7.0
|
||||
flake8-json==24.4.0
|
||||
flake8-noqa==1.4.0
|
||||
flake8-polyfill==1.0.2
|
||||
flake8-use-fstring==1.4
|
||||
|
||||
mypy==1.9.0
|
||||
pep8-naming==0.13.3
|
||||
safety==3.0.1
|
||||
safety==3.2.0
|
||||
types-requests==2.31.0.20240311
|
||||
types-pkg-resources==0.1.3
|
||||
|
||||
@ -38,9 +38,9 @@ grip==4.6.2
|
||||
py2exe==0.13.0.1; sys_platform == 'win32'
|
||||
|
||||
# Testing
|
||||
pytest==8.1.1
|
||||
pytest-cov==4.1.0 # Pytest code coverage support
|
||||
coverage[toml]==7.4.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||
pytest==8.2.0
|
||||
pytest-cov==5.0.0 # Pytest code coverage support
|
||||
coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||
coverage-conditional-plugin==0.9.0
|
||||
# For manipulating folder permissions and the like.
|
||||
pywin32==306; sys_platform == 'win32'
|
||||
|
@ -1,12 +1,5 @@
|
||||
certifi==2024.2.2
|
||||
requests==2.31.0
|
||||
pillow==10.3.0
|
||||
# requests depends on this now ?
|
||||
charset-normalizer==3.3.2
|
||||
|
||||
watchdog==4.0.0
|
||||
# Commented out because this doesn't package well with py2exe
|
||||
infi.systray==0.1.12; sys_platform == 'win32'
|
||||
# argh==0.26.2 watchdog dep
|
||||
# pyyaml==5.3.1 watchdog dep
|
||||
semantic-version==2.10.0
|
||||
|
@ -38,8 +38,9 @@ def find_calls_in_stmt(statement: ast.AST) -> list[ast.Call]:
|
||||
out = []
|
||||
for n in ast.iter_child_nodes(statement):
|
||||
out.extend(find_calls_in_stmt(n))
|
||||
if isinstance(statement, ast.Call) and get_func_name(statement.func) == '_':
|
||||
out.append(statement)
|
||||
if isinstance(statement, ast.Call) and get_func_name(statement.func) in ('tr', 'translations'):
|
||||
if ast.unparse(statement).find('.tl') != -1 or ast.unparse(statement).find('translate') != -1:
|
||||
out.append(statement)
|
||||
return out
|
||||
|
||||
|
||||
|
@ -89,6 +89,9 @@
|
||||
"Python": {
|
||||
"hullMass": 350
|
||||
},
|
||||
"Python Mk II": {
|
||||
"hullMass": 450
|
||||
},
|
||||
"Sidewinder": {
|
||||
"hullMass": 25
|
||||
},
|
||||
|
235
stats.py
235
stats.py
@ -12,20 +12,17 @@ import json
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Any, AnyStr, Callable, NamedTuple, Sequence, cast
|
||||
from typing import Any, AnyStr, Callable, NamedTuple, Sequence, cast
|
||||
import companion
|
||||
import EDMCLogging
|
||||
import myNotebook as nb # noqa: N813
|
||||
from edmc_data import ship_name_map
|
||||
from hotkey import hotkeymgr
|
||||
from l10n import Locale
|
||||
from l10n import Locale, translations as tr
|
||||
from monitor import monitor
|
||||
|
||||
logger = EDMCLogging.get_main_logger()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str: return x
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT
|
||||
@ -60,32 +57,32 @@ def status(data: dict[str, Any]) -> list[list[str]]:
|
||||
"""
|
||||
# StatsResults assumes these three things are first
|
||||
res = [
|
||||
[_('Cmdr'), data['commander']['name']], # LANG: Cmdr stats
|
||||
[_('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats
|
||||
[_('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats
|
||||
[tr.tl('Cmdr'), data['commander']['name']], # LANG: Cmdr stats
|
||||
[tr.tl('Balance'), str(data['commander'].get('credits', 0))], # LANG: Cmdr stats
|
||||
[tr.tl('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats
|
||||
]
|
||||
|
||||
_ELITE_RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime
|
||||
_('Elite'), # LANG: Top rank
|
||||
_('Elite I'), # LANG: Top rank +1
|
||||
_('Elite II'), # LANG: Top rank +2
|
||||
_('Elite III'), # LANG: Top rank +3
|
||||
_('Elite IV'), # LANG: Top rank +4
|
||||
_('Elite V'), # LANG: Top rank +5
|
||||
tr.tl('Elite'), # LANG: Top rank
|
||||
tr.tl('Elite I'), # LANG: Top rank +1
|
||||
tr.tl('Elite II'), # LANG: Top rank +2
|
||||
tr.tl('Elite III'), # LANG: Top rank +3
|
||||
tr.tl('Elite IV'), # LANG: Top rank +4
|
||||
tr.tl('Elite V'), # LANG: Top rank +5
|
||||
]
|
||||
|
||||
RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime
|
||||
# in output order
|
||||
# Names we show people, vs internal names
|
||||
(_('Combat'), 'combat'), # LANG: Ranking
|
||||
(_('Trade'), 'trade'), # LANG: Ranking
|
||||
(_('Explorer'), 'explore'), # LANG: Ranking
|
||||
(_('Mercenary'), 'soldier'), # LANG: Ranking
|
||||
(_('Exobiologist'), 'exobiologist'), # LANG: Ranking
|
||||
(_('CQC'), 'cqc'), # LANG: Ranking
|
||||
(_('Federation'), 'federation'), # LANG: Ranking
|
||||
(_('Empire'), 'empire'), # LANG: Ranking
|
||||
(_('Powerplay'), 'power'), # LANG: Ranking
|
||||
(tr.tl('Combat'), 'combat'), # LANG: Ranking
|
||||
(tr.tl('Trade'), 'trade'), # LANG: Ranking
|
||||
(tr.tl('Explorer'), 'explore'), # LANG: Ranking
|
||||
(tr.tl('Mercenary'), 'soldier'), # LANG: Ranking
|
||||
(tr.tl('Exobiologist'), 'exobiologist'), # LANG: Ranking
|
||||
(tr.tl('CQC'), 'cqc'), # LANG: Ranking
|
||||
(tr.tl('Federation'), 'federation'), # LANG: Ranking
|
||||
(tr.tl('Empire'), 'empire'), # LANG: Ranking
|
||||
(tr.tl('Powerplay'), 'power'), # LANG: Ranking
|
||||
# ??? , 'crime'), # LANG: Ranking
|
||||
# ??? , 'service'), # LANG: Ranking
|
||||
]
|
||||
@ -94,113 +91,113 @@ def status(data: dict[str, Any]) -> list[list[str]]:
|
||||
# These names are the fdev side name (but lower()ed)
|
||||
# http://elite-dangerous.wikia.com/wiki/Pilots_Federation#Ranks
|
||||
'combat': [
|
||||
_('Harmless'), # LANG: Combat rank
|
||||
_('Mostly Harmless'), # LANG: Combat rank
|
||||
_('Novice'), # LANG: Combat rank
|
||||
_('Competent'), # LANG: Combat rank
|
||||
_('Expert'), # LANG: Combat rank
|
||||
_('Master'), # LANG: Combat rank
|
||||
_('Dangerous'), # LANG: Combat rank
|
||||
_('Deadly'), # LANG: Combat rank
|
||||
tr.tl('Harmless'), # LANG: Combat rank
|
||||
tr.tl('Mostly Harmless'), # LANG: Combat rank
|
||||
tr.tl('Novice'), # LANG: Combat rank
|
||||
tr.tl('Competent'), # LANG: Combat rank
|
||||
tr.tl('Expert'), # LANG: Combat rank
|
||||
tr.tl('Master'), # LANG: Combat rank
|
||||
tr.tl('Dangerous'), # LANG: Combat rank
|
||||
tr.tl('Deadly'), # LANG: Combat rank
|
||||
] + _ELITE_RANKS,
|
||||
'trade': [
|
||||
_('Penniless'), # LANG: Trade rank
|
||||
_('Mostly Penniless'), # LANG: Trade rank
|
||||
_('Peddler'), # LANG: Trade rank
|
||||
_('Dealer'), # LANG: Trade rank
|
||||
_('Merchant'), # LANG: Trade rank
|
||||
_('Broker'), # LANG: Trade rank
|
||||
_('Entrepreneur'), # LANG: Trade rank
|
||||
_('Tycoon'), # LANG: Trade rank
|
||||
tr.tl('Penniless'), # LANG: Trade rank
|
||||
tr.tl('Mostly Penniless'), # LANG: Trade rank
|
||||
tr.tl('Peddler'), # LANG: Trade rank
|
||||
tr.tl('Dealer'), # LANG: Trade rank
|
||||
tr.tl('Merchant'), # LANG: Trade rank
|
||||
tr.tl('Broker'), # LANG: Trade rank
|
||||
tr.tl('Entrepreneur'), # LANG: Trade rank
|
||||
tr.tl('Tycoon'), # LANG: Trade rank
|
||||
] + _ELITE_RANKS,
|
||||
'explore': [
|
||||
_('Aimless'), # LANG: Explorer rank
|
||||
_('Mostly Aimless'), # LANG: Explorer rank
|
||||
_('Scout'), # LANG: Explorer rank
|
||||
_('Surveyor'), # LANG: Explorer rank
|
||||
_('Trailblazer'), # LANG: Explorer rank
|
||||
_('Pathfinder'), # LANG: Explorer rank
|
||||
_('Ranger'), # LANG: Explorer rank
|
||||
_('Pioneer'), # LANG: Explorer rank
|
||||
tr.tl('Aimless'), # LANG: Explorer rank
|
||||
tr.tl('Mostly Aimless'), # LANG: Explorer rank
|
||||
tr.tl('Scout'), # LANG: Explorer rank
|
||||
tr.tl('Surveyor'), # LANG: Explorer rank
|
||||
tr.tl('Trailblazer'), # LANG: Explorer rank
|
||||
tr.tl('Pathfinder'), # LANG: Explorer rank
|
||||
tr.tl('Ranger'), # LANG: Explorer rank
|
||||
tr.tl('Pioneer'), # LANG: Explorer rank
|
||||
|
||||
] + _ELITE_RANKS,
|
||||
'soldier': [
|
||||
_('Defenceless'), # LANG: Mercenary rank
|
||||
_('Mostly Defenceless'), # LANG: Mercenary rank
|
||||
_('Rookie'), # LANG: Mercenary rank
|
||||
_('Soldier'), # LANG: Mercenary rank
|
||||
_('Gunslinger'), # LANG: Mercenary rank
|
||||
_('Warrior'), # LANG: Mercenary rank
|
||||
_('Gunslinger'), # LANG: Mercenary rank
|
||||
_('Deadeye'), # LANG: Mercenary rank
|
||||
tr.tl('Defenceless'), # LANG: Mercenary rank
|
||||
tr.tl('Mostly Defenceless'), # LANG: Mercenary rank
|
||||
tr.tl('Rookie'), # LANG: Mercenary rank
|
||||
tr.tl('Soldier'), # LANG: Mercenary rank
|
||||
tr.tl('Gunslinger'), # LANG: Mercenary rank
|
||||
tr.tl('Warrior'), # LANG: Mercenary rank
|
||||
tr.tl('Gunslinger'), # LANG: Mercenary rank
|
||||
tr.tl('Deadeye'), # LANG: Mercenary rank
|
||||
] + _ELITE_RANKS,
|
||||
'exobiologist': [
|
||||
_('Directionless'), # LANG: Exobiologist rank
|
||||
_('Mostly Directionless'), # LANG: Exobiologist rank
|
||||
_('Compiler'), # LANG: Exobiologist rank
|
||||
_('Collector'), # LANG: Exobiologist rank
|
||||
_('Cataloguer'), # LANG: Exobiologist rank
|
||||
_('Taxonomist'), # LANG: Exobiologist rank
|
||||
_('Ecologist'), # LANG: Exobiologist rank
|
||||
_('Geneticist'), # LANG: Exobiologist rank
|
||||
tr.tl('Directionless'), # LANG: Exobiologist rank
|
||||
tr.tl('Mostly Directionless'), # LANG: Exobiologist rank
|
||||
tr.tl('Compiler'), # LANG: Exobiologist rank
|
||||
tr.tl('Collector'), # LANG: Exobiologist rank
|
||||
tr.tl('Cataloguer'), # LANG: Exobiologist rank
|
||||
tr.tl('Taxonomist'), # LANG: Exobiologist rank
|
||||
tr.tl('Ecologist'), # LANG: Exobiologist rank
|
||||
tr.tl('Geneticist'), # LANG: Exobiologist rank
|
||||
] + _ELITE_RANKS,
|
||||
'cqc': [
|
||||
_('Helpless'), # LANG: CQC rank
|
||||
_('Mostly Helpless'), # LANG: CQC rank
|
||||
_('Amateur'), # LANG: CQC rank
|
||||
_('Semi Professional'), # LANG: CQC rank
|
||||
_('Professional'), # LANG: CQC rank
|
||||
_('Champion'), # LANG: CQC rank
|
||||
_('Hero'), # LANG: CQC rank
|
||||
_('Gladiator'), # LANG: CQC rank
|
||||
tr.tl('Helpless'), # LANG: CQC rank
|
||||
tr.tl('Mostly Helpless'), # LANG: CQC rank
|
||||
tr.tl('Amateur'), # LANG: CQC rank
|
||||
tr.tl('Semi Professional'), # LANG: CQC rank
|
||||
tr.tl('Professional'), # LANG: CQC rank
|
||||
tr.tl('Champion'), # LANG: CQC rank
|
||||
tr.tl('Hero'), # LANG: CQC rank
|
||||
tr.tl('Gladiator'), # LANG: CQC rank
|
||||
] + _ELITE_RANKS,
|
||||
|
||||
# http://elite-dangerous.wikia.com/wiki/Federation#Ranks
|
||||
'federation': [
|
||||
_('None'), # LANG: No rank
|
||||
_('Recruit'), # LANG: Federation rank
|
||||
_('Cadet'), # LANG: Federation rank
|
||||
_('Midshipman'), # LANG: Federation rank
|
||||
_('Petty Officer'), # LANG: Federation rank
|
||||
_('Chief Petty Officer'), # LANG: Federation rank
|
||||
_('Warrant Officer'), # LANG: Federation rank
|
||||
_('Ensign'), # LANG: Federation rank
|
||||
_('Lieutenant'), # LANG: Federation rank
|
||||
_('Lieutenant Commander'), # LANG: Federation rank
|
||||
_('Post Commander'), # LANG: Federation rank
|
||||
_('Post Captain'), # LANG: Federation rank
|
||||
_('Rear Admiral'), # LANG: Federation rank
|
||||
_('Vice Admiral'), # LANG: Federation rank
|
||||
_('Admiral') # LANG: Federation rank
|
||||
tr.tl('None'), # LANG: No rank
|
||||
tr.tl('Recruit'), # LANG: Federation rank
|
||||
tr.tl('Cadet'), # LANG: Federation rank
|
||||
tr.tl('Midshipman'), # LANG: Federation rank
|
||||
tr.tl('Petty Officer'), # LANG: Federation rank
|
||||
tr.tl('Chief Petty Officer'), # LANG: Federation rank
|
||||
tr.tl('Warrant Officer'), # LANG: Federation rank
|
||||
tr.tl('Ensign'), # LANG: Federation rank
|
||||
tr.tl('Lieutenant'), # LANG: Federation rank
|
||||
tr.tl('Lieutenant Commander'), # LANG: Federation rank
|
||||
tr.tl('Post Commander'), # LANG: Federation rank
|
||||
tr.tl('Post Captain'), # LANG: Federation rank
|
||||
tr.tl('Rear Admiral'), # LANG: Federation rank
|
||||
tr.tl('Vice Admiral'), # LANG: Federation rank
|
||||
tr.tl('Admiral') # LANG: Federation rank
|
||||
],
|
||||
|
||||
# http://elite-dangerous.wikia.com/wiki/Empire#Ranks
|
||||
'empire': [
|
||||
_('None'), # LANG: No rank
|
||||
_('Outsider'), # LANG: Empire rank
|
||||
_('Serf'), # LANG: Empire rank
|
||||
_('Master'), # LANG: Empire rank
|
||||
_('Squire'), # LANG: Empire rank
|
||||
_('Knight'), # LANG: Empire rank
|
||||
_('Lord'), # LANG: Empire rank
|
||||
_('Baron'), # LANG: Empire rank
|
||||
_('Viscount'), # LANG: Empire rank
|
||||
_('Count'), # LANG: Empire rank
|
||||
_('Earl'), # LANG: Empire rank
|
||||
_('Marquis'), # LANG: Empire rank
|
||||
_('Duke'), # LANG: Empire rank
|
||||
_('Prince'), # LANG: Empire rank
|
||||
_('King') # LANG: Empire rank
|
||||
tr.tl('None'), # LANG: No rank
|
||||
tr.tl('Outsider'), # LANG: Empire rank
|
||||
tr.tl('Serf'), # LANG: Empire rank
|
||||
tr.tl('Master'), # LANG: Empire rank
|
||||
tr.tl('Squire'), # LANG: Empire rank
|
||||
tr.tl('Knight'), # LANG: Empire rank
|
||||
tr.tl('Lord'), # LANG: Empire rank
|
||||
tr.tl('Baron'), # LANG: Empire rank
|
||||
tr.tl('Viscount'), # LANG: Empire rank
|
||||
tr.tl('Count'), # LANG: Empire rank
|
||||
tr.tl('Earl'), # LANG: Empire rank
|
||||
tr.tl('Marquis'), # LANG: Empire rank
|
||||
tr.tl('Duke'), # LANG: Empire rank
|
||||
tr.tl('Prince'), # LANG: Empire rank
|
||||
tr.tl('King') # LANG: Empire rank
|
||||
],
|
||||
|
||||
# http://elite-dangerous.wikia.com/wiki/Ratings
|
||||
'power': [
|
||||
_('None'), # LANG: No rank
|
||||
_('Rating 1'), # LANG: Power rank
|
||||
_('Rating 2'), # LANG: Power rank
|
||||
_('Rating 3'), # LANG: Power rank
|
||||
_('Rating 4'), # LANG: Power rank
|
||||
_('Rating 5') # LANG: Power rank
|
||||
tr.tl('None'), # LANG: No rank
|
||||
tr.tl('Rating 1'), # LANG: Power rank
|
||||
tr.tl('Rating 2'), # LANG: Power rank
|
||||
tr.tl('Rating 3'), # LANG: Power rank
|
||||
tr.tl('Rating 4'), # LANG: Power rank
|
||||
tr.tl('Rating 5') # LANG: Power rank
|
||||
],
|
||||
}
|
||||
|
||||
@ -212,7 +209,7 @@ def status(data: dict[str, Any]) -> list[list[str]]:
|
||||
res.append([title, names[rank] if rank < len(names) else f'Rank {rank}'])
|
||||
|
||||
else:
|
||||
res.append([title, _('None')]) # LANG: No rank
|
||||
res.append([title, tr.tl('None')]) # LANG: No rank
|
||||
|
||||
return res
|
||||
|
||||
@ -318,7 +315,7 @@ class StatsDialog():
|
||||
if not monitor.cmdr:
|
||||
hotkeymgr.play_bad()
|
||||
# LANG: Current commander unknown when trying to use 'File' > 'Status'
|
||||
self.status['text'] = _("Status: Don't yet know your Commander name")
|
||||
self.status['text'] = tr.tl("Status: Don't yet know your Commander name")
|
||||
return
|
||||
|
||||
# TODO: This needs to use cached data
|
||||
@ -326,7 +323,7 @@ class StatsDialog():
|
||||
logger.info('No cached data, aborting...')
|
||||
hotkeymgr.play_bad()
|
||||
# LANG: No Frontier CAPI data yet when trying to use 'File' > 'Status'
|
||||
self.status['text'] = _("Status: No CAPI data yet")
|
||||
self.status['text'] = tr.tl("Status: No CAPI data yet")
|
||||
return
|
||||
|
||||
capi_data = json.loads(
|
||||
@ -336,7 +333,7 @@ class StatsDialog():
|
||||
if not capi_data.get('commander') or not capi_data['commander'].get('name', '').strip():
|
||||
# Shouldn't happen
|
||||
# LANG: Unknown commander
|
||||
self.status['text'] = _("Who are you?!")
|
||||
self.status['text'] = tr.tl("Who are you?!")
|
||||
|
||||
elif (
|
||||
not capi_data.get('lastSystem')
|
||||
@ -344,7 +341,7 @@ class StatsDialog():
|
||||
):
|
||||
# Shouldn't happen
|
||||
# LANG: Unknown location
|
||||
self.status['text'] = _("Where are you?!")
|
||||
self.status['text'] = tr.tl("Where are you?!")
|
||||
|
||||
elif (
|
||||
not capi_data.get('ship') or not capi_data['ship'].get('modules')
|
||||
@ -352,7 +349,7 @@ class StatsDialog():
|
||||
):
|
||||
# Shouldn't happen
|
||||
# LANG: Unknown ship
|
||||
self.status['text'] = _("What are you flying?!")
|
||||
self.status['text'] = tr.tl("What are you flying?!")
|
||||
|
||||
else:
|
||||
self.status['text'] = ''
|
||||
@ -401,14 +398,14 @@ class StatsResults(tk.Toplevel):
|
||||
self.addpagerow(page, thing, with_copy=True)
|
||||
|
||||
ttk.Frame(page).grid(pady=5) # bottom spacer
|
||||
notebook.add(page, text=_('Status')) # LANG: Status dialog title
|
||||
notebook.add(page, text=tr.tl('Status')) # LANG: Status dialog title
|
||||
|
||||
page = self.addpage(notebook, [
|
||||
_('Ship'), # LANG: Status dialog subtitle
|
||||
tr.tl('Ship'), # LANG: Status dialog subtitle
|
||||
'',
|
||||
_('System'), # LANG: Main window
|
||||
_('Station'), # LANG: Status dialog subtitle
|
||||
_('Value'), # LANG: Status dialog subtitle - CR value of ship
|
||||
tr.tl('System'), # LANG: Main window
|
||||
tr.tl('Station'), # LANG: Status dialog subtitle
|
||||
tr.tl('Value'), # LANG: Status dialog subtitle - CR value of ship
|
||||
])
|
||||
|
||||
shiplist = ships(data)
|
||||
@ -417,7 +414,7 @@ class StatsResults(tk.Toplevel):
|
||||
self.addpagerow(page, list(ship_data[1:-1]) + [self.credits(int(ship_data[-1]))], with_copy=True)
|
||||
|
||||
ttk.Frame(page).grid(pady=5) # bottom spacer
|
||||
notebook.add(page, text=_('Ships')) # LANG: Status dialog title
|
||||
notebook.add(page, text=tr.tl('Ships')) # LANG: Status dialog title
|
||||
|
||||
# wait for window to appear on screen before calling grab_set
|
||||
self.wait_visibility()
|
||||
|
9
theme.py
9
theme.py
@ -16,17 +16,14 @@ import tkinter as tk
|
||||
from os.path import join
|
||||
from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
|
||||
from typing import Callable
|
||||
from l10n import translations as tr
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str: ...
|
||||
|
||||
if __debug__:
|
||||
from traceback import print_exc
|
||||
|
||||
@ -291,7 +288,7 @@ class _Theme:
|
||||
# Font only supports Latin 1 / Supplement / Extended, and a
|
||||
# few General Punctuation and Mathematical Operators
|
||||
# LANG: Label for commander name in main window
|
||||
'font': (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and
|
||||
'font': (theme > 1 and not 0x250 < ord(tr.tl('Cmdr')[0]) < 0x3000 and
|
||||
tk_font.Font(family='Euro Caps', size=10, weight=tk_font.NORMAL) or
|
||||
'TkDefaultFont'),
|
||||
}
|
||||
|
@ -25,10 +25,8 @@ import tkinter as tk
|
||||
import webbrowser
|
||||
from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str: return x
|
||||
from typing import Any
|
||||
from l10n import translations as tr
|
||||
|
||||
|
||||
class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
@ -55,7 +53,7 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
|
||||
self.menu = tk.Menu(tearoff=tk.FALSE)
|
||||
# LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||
self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste
|
||||
self.menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste
|
||||
self.bind('<Button-3>', self._contextmenu)
|
||||
|
||||
self.bind('<Enter>', self._enter)
|
||||
|
@ -16,10 +16,10 @@ import requests
|
||||
import semantic_version
|
||||
from config import appname, appversion_nobuild, config, update_feed
|
||||
from EDMCLogging import get_main_logger
|
||||
from l10n import translations as tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import tkinter as tk
|
||||
def _(x: str): return x
|
||||
|
||||
|
||||
logger = get_main_logger()
|
||||
@ -200,7 +200,7 @@ class Updater:
|
||||
if newversion and self.root:
|
||||
status = self.root.nametowidget(f'.{appname.lower()}.status')
|
||||
# LANG: Update Available Text
|
||||
status['text'] = _("{NEWVER} is available").format(NEWVER=newversion.title)
|
||||
status['text'] = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title)
|
||||
self.root.update_idletasks()
|
||||
|
||||
else:
|
||||
|
@ -5,18 +5,21 @@ Copyright (c) EDCD, All Rights Reserved
|
||||
Licensed under the GNU General Public License.
|
||||
See LICENSE file.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from edmc_data import ship_name_map
|
||||
|
||||
|
||||
def ship_file_name(ship_name: str, ship_type: str) -> str:
|
||||
"""Return a ship name suitable for a filename."""
|
||||
name = str(ship_name or ship_name_map.get(ship_type.lower(), ship_type)).strip()
|
||||
if name.endswith('.'):
|
||||
name = name[:-2]
|
||||
|
||||
if name.lower() in ('con', 'prn', 'aux', 'nul',
|
||||
'com0', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
|
||||
'lpt0', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'):
|
||||
name += '_'
|
||||
# Handle suffix using Pathlib's with_suffix method
|
||||
name = Path(name).with_suffix("").name
|
||||
|
||||
return name.translate({ord(x): '_' for x in ('\0', '<', '>', ':', '"', '/', '\\', '|', '?', '*')})
|
||||
# Check if the name is a reserved filename
|
||||
if Path(name).is_reserved():
|
||||
name += "_"
|
||||
|
||||
return name.translate(
|
||||
{ord(x): "_" for x in ("\0", "<", ">", ":", '"', "/", "\\", "|", "?", "*")}
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user