Merge branch 'main' into stable
1
.flake8
@ -7,7 +7,6 @@ exclude =
|
||||
FDevIDs/
|
||||
venv/
|
||||
.venv/
|
||||
hotkey/darwin.py # FIXME: Check under macOS VM at some point
|
||||
|
||||
# Show exactly where in a line the error happened
|
||||
#show-source = True
|
||||
|
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,22 +7,19 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please check the [Known Issues](https://github.com/EDCD/EDMarketConnector/issues/618) in case this has already been reported.**
|
||||
|
||||
**Please also check if the issue is covered in our [Troubleshooting Guide](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting).** It might be something with a known work around, or where a third party (such as EDSM) is causing logging that is harmless.
|
||||
|
||||
**Please complete the following information:**
|
||||
- Version: [e.g. 4.0.6 - See 'Help > About E:D Market Connector'. If running from source using git then please paste the output of `git log --decorate=full | head -1`]
|
||||
|
||||
[//]: # (You can gather most of this information with the EDMC System Profiler)
|
||||
|
||||
- Version: [e.g. 5.10.4+39af6c34`]
|
||||
- Game Version: [e.g. 'Live' or 'Odyssey']
|
||||
- OS: [e.g. Windows 10, Linux Debian 10.6, etc.]
|
||||
- OS Locale: [e.g. English, French, Serbian...]
|
||||
- If applicable: Browser [e.g. chrome, safari]
|
||||
- Please attach **BOTH** log files, by dragging and dropping them into this input:
|
||||
1. `%TEMP%\EDMarketConnector.log` from *immediately* after the bug occurs (re-running the application overwrites this file).
|
||||
1. `%TEMP%\EDMarketConnector\EDMarketConnector-debug.log`. See [Debug Log File](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#debug-log-files). NB: If you don't have this log file then you're not running the latest version of the application and should update first to see if we already fixed the bug you're reporting.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
[//]: # (A clear and concise description of what the bug is.)
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
@ -32,10 +29,24 @@ Steps to reproduce the behavior:
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
[//]: # (A clear and concise description of what you expected to happen.)
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
[//]: # (If applicable, add screenshots to help explain your problem.)
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
**Please Confirm the Following...**
|
||||
|
||||
[//]: # (Add any other context about the problem here.)
|
||||
- [ ] I have checked the [Known Issues](https://github.com/EDCD/EDMarketConnector/issues/618) list to ensure this is not a duplicate
|
||||
- [ ] I have checked the [Troubleshooting Guide](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting) to check for known workarounds
|
||||
|
||||
**Logs**
|
||||
Please attach both the EDMarketConnector.log and EDMarketConnector-debug.log if available.
|
||||
|
||||
You can find these logs at `%TEMP%\EDMarketConnector.log` and `%TEMP%\EDMarketConnector\EDMarketConnector-debug.log`
|
||||
|
||||
See [Debug Log File](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#debug-log-files) for information on the Debug Log files
|
||||
|
3
.github/workflows/pr-checks.yml
vendored
@ -13,7 +13,7 @@ on:
|
||||
|
||||
jobs:
|
||||
code-checks:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
@ -56,6 +56,7 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version-file: '.python-version'
|
||||
cache: 'pip' # caching pip dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
3
.github/workflows/push-checks.yml
vendored
@ -19,7 +19,7 @@ on:
|
||||
jobs:
|
||||
push_checks:
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -28,6 +28,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
cache: 'pip' # caching pip dependencies
|
||||
python-version-file: '.python-version'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
26
.github/workflows/windows-build.yml
vendored
@ -62,16 +62,16 @@ jobs:
|
||||
mv ../EDMarketConnector-release-${{ needs.variables.outputs.sem_ver }}.tar.gz .
|
||||
|
||||
- name: Upload build files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Built files
|
||||
name: built-files-linux
|
||||
path: |
|
||||
EDMarketConnector-release-*.tar.gz
|
||||
|
||||
windows_build:
|
||||
needs: [variables]
|
||||
name: Build EDMC
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@ -105,6 +105,7 @@ jobs:
|
||||
with:
|
||||
python-version-file: '.python-version'
|
||||
architecture: "x86"
|
||||
cache: 'pip' # caching pip dependencies
|
||||
|
||||
- name: Install python tools
|
||||
run: |
|
||||
@ -131,22 +132,31 @@ jobs:
|
||||
Get-ChildItem -Path . -Filter "EDMarketConnector_Installer_*.exe" | Rename-Item -NewName {"EDMarketConnector_Installer_Unsigned_$($_.Name -replace '^EDMarketConnector_Installer_', '')"}
|
||||
|
||||
- name: Upload build files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Built files
|
||||
name: built-files-windows
|
||||
path: |
|
||||
EDMarketConnector_Installer_*.exe
|
||||
EDMarketConnector-release-*.zip
|
||||
Merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ windows_build, linux_build ]
|
||||
steps:
|
||||
- name: Merge Artifacts
|
||||
uses: actions/upload-artifact/merge@v4
|
||||
with:
|
||||
name: Built files
|
||||
pattern: built-files-*
|
||||
|
||||
release:
|
||||
name: Release new version
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ windows_build, linux_build ]
|
||||
needs: Merge
|
||||
if: "${{ github.event_name != 'workflow_dispatch' }}"
|
||||
|
||||
steps:
|
||||
- name: Download binary
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: Built files
|
||||
path: ./
|
||||
@ -155,7 +165,7 @@ jobs:
|
||||
run: sha256sum EDMarketConnector_Installer_*.exe EDMarketConnector-release-*.{zip,tar.gz} > ./hashes.sum
|
||||
|
||||
- name: Create Draft Release
|
||||
uses: "softprops/action-gh-release@v1"
|
||||
uses: "softprops/action-gh-release@v2"
|
||||
with:
|
||||
token: "${{secrets.GITHUB_TOKEN}}"
|
||||
draft: true
|
||||
|
1
.gitignore
vendored
@ -58,3 +58,4 @@ pylint.txt
|
||||
|
||||
# Ignore Submodule data directory
|
||||
coriolis-data/
|
||||
FDevIDs/
|
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
||||
[submodule "coriolis-data"]
|
||||
path = coriolis-data
|
||||
url = https://github.com/EDCD/coriolis-data.git
|
||||
[submodule "FDevIDs"]
|
||||
path = FDevIDs
|
||||
url = https://github.com/EDCD/FDevIDs.git
|
||||
|
@ -6,5 +6,4 @@ scripts_are_modules = True
|
||||
; `<var> = <value>`
|
||||
; i.e. no typing info.
|
||||
check_untyped_defs = True
|
||||
; platform = darwin
|
||||
explicit_package_bases = True
|
||||
|
@ -1 +1 @@
|
||||
3.11.7
|
||||
3.11.9
|
||||
|
71
ChangeLog.md
@ -6,6 +6,73 @@ 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.11.0
|
||||
===
|
||||
|
||||
This release includes a number of new features and improvements, including a new Beta Update Track for testing future updates, enhanced context menus for text entry fields and UI elements, a revamp to the existing translation system and logging capabilities, and more. This release includes the Python Image Library (PIL) into our core bundle, adds a number of stability and configuration checks to the tool, and adds new schemas and configuration values to senders.
|
||||
|
||||
This release also includes a number of bug fixes, performance enhancements, and updates to various aspects of the code to enhance maintainability are included. Notably, MacOS support has been removed due to a lack of support for this OS in Elite, and a number of functions have been deprecated and will be removed in later versions. Plugin developers, take note!
|
||||
|
||||
**Changes and Enhancements**
|
||||
* Established a Beta Update Track to allow users to assist in future update testing
|
||||
* Added a global context menu for text entry fields that includes cut, copy, and paste options
|
||||
* Added a context menu for Ship, System, and Station UI elements which allows opening the respective link in any of the available resource providers.
|
||||
* Added translation hooks to the update available status string
|
||||
* Added additional status logging when we're awaiting game log-in
|
||||
* Added the Python Image Library (PIL) to the core EDMC library bundle
|
||||
* Added respect for EDSM API limits to the default plugin
|
||||
* Added EDDN stationType and carrierDockingAccess schemas to the sent events
|
||||
* Added MaxJumpRange and CargoCapacity events to the Inara sender
|
||||
* Added a high-level critical error handler to gracefully terminate the program in the event of a catastrophic error
|
||||
* Added the ability to override the default language for a translation by adding the optional 'lang' parameter to the translate function for individual functions
|
||||
* Added an updated template and new security reporting guidance to the documentation
|
||||
* Added a new updater for the FDevID Files to keep the dependency up to date without requiring a new patch version push
|
||||
* Added a System Profiler Utility to assist with gathering system and environment information for bug report purposes
|
||||
* Added a new security policy for responsible disclosure of identified security issues
|
||||
* Adds Additional Error Processing to the System Profiler when launched from EDMC
|
||||
* Adds the ability to resize the Settings window to larger than the initial default size
|
||||
* Enabled security code scanning on the GitHub repository
|
||||
* Tweaked a few list length checks that could just be boolean to be bool
|
||||
* Updates the look and feel of the "Already Running" popup to reduce overhead and improve the look of the popup
|
||||
* Updated translations to latest versions, including a new language: Ukranian!
|
||||
* Updated documentation to reflect certain changes to the code
|
||||
* Updated the GitHub Bug Report template
|
||||
* Updated the GitHub Pull Request template
|
||||
* Updated internal workflows to more recent versions
|
||||
* Updated util_ships to avoid using Windows reserved file names as output
|
||||
* Converted all usages of the unnecessary OrderedDict to use the standard dict
|
||||
* Clarifies the hierarchy of parent classes for custom MyNotebook classes
|
||||
* Renamed the default translation function from `_()` to `tr.tl()`
|
||||
* Renamed the Translations base class to conform to Pythonic standards
|
||||
* Deprecated the `_Translations` class
|
||||
* Deprecated the `Translations` singleton in favor of `translations`
|
||||
* Unpinned several dependencies that were already dependencies of other dependencies to prevent dependency conflicts (say that 5 times fast)
|
||||
* Updated a few type hints to allow updates to more updated dependencies
|
||||
* Changed the translation function import to no longer rely on forcing it into Python's builtins
|
||||
* Handed over a few tk classes to their ttk equivalents for better styling
|
||||
* Reworked the Plugin system to no longer use the deprecated importlib.load_module()
|
||||
* Deprecated nb.Entry and nb.ColoredButton as they simply point toward other classes with no processing
|
||||
* Removed macOS support
|
||||
* Removed deprecated modules.p and ships.p files
|
||||
* Removed deprecated openurl() function
|
||||
|
||||
**Bug Fixes**
|
||||
* Fixed a bug where certain types of exceptions from the Requests module wouldn't be handled properly regarding killswitches
|
||||
* Fixed a rare bug where source builds running on 64-bit Python could generate an OverflowError in the monitor system
|
||||
* Fixed a bug where EDMC would open directories in the webbrowser instead of the file explorer on Linux
|
||||
* Fixed a rare bug that could cause the EDSM plugin to crash due to missing configuration values
|
||||
|
||||
**Plugin Developers**
|
||||
* nb.Entry is deprecated, and is slated for removal in 6.0 or later. Please migrate to nb.EntryMenu
|
||||
* nb.ColoredButton is deprecated, and is slated for removal in 6.0 or later. Please migrate to tk.Button
|
||||
* Calling internal translations with `_()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to importing `translations` and calling `translations.translate` or `translations.tl` directly
|
||||
* `Translations` as the translate system singleton is deprecated, and is slated for removal in 6.0 or later. Please migrate to the `translations` singleton
|
||||
* `help_open_log_folder()` is deprecated, and is slated for removal in 6.0 or later. Please migrate to open_folder()
|
||||
* `update_feed` is deprecated, and is slated for removal in 6.0 or later. Please migrate to `get_update_feed()`.
|
||||
* modules.p and ships.p are deprecated, and have been removed
|
||||
* The `openurl()` function in ttkHyperlinkLabel has been removed. Please migrate to `webbrowser.open()`
|
||||
|
||||
|
||||
Release 5.10.6
|
||||
===
|
||||
This release contains the data information for the new SCO modules added in Elite update 18.04.
|
||||
@ -175,7 +242,7 @@ for removal in the next major release! Please look for that change coming soon.
|
||||
|
||||
**Changes and Enhancements**
|
||||
* Added new `modules.json` and `ships.json` files to improve security and readability
|
||||
* Added a core Spanch URL provider plugin
|
||||
* Added a core Spansh URL provider plugin
|
||||
* Added a new auth response page for successful FDEV authentication
|
||||
* Added a new Open Log Folder option to the Help menu
|
||||
* Added a new `--start_min` command flag to force the application to start minimized
|
||||
@ -241,7 +308,7 @@ Known Issues
|
||||
|
||||
Release 5.9.3
|
||||
===
|
||||
This release is identical to 5.9.3, except reverts a bad change.
|
||||
This release is identical to 5.9.2, except reverts a bad change.
|
||||
|
||||
- REVERTS Deprecated load_module() is now retired (#1462)
|
||||
|
||||
|
@ -679,7 +679,7 @@ the following does not work:
|
||||
|
||||
```py
|
||||
from sys import platform
|
||||
if platform == 'darwin':
|
||||
if platform == 'win32':
|
||||
...
|
||||
```
|
||||
|
||||
|
16
EDMC.py
@ -34,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
|
||||
@ -42,7 +42,7 @@ import stats
|
||||
from commodity import COMMODITY_DEFAULT
|
||||
from config import appcmdname, appversion, config
|
||||
from monitor import monitor
|
||||
from update import EDMCVersion, Updater
|
||||
from update import EDMCVersion, Updater, check_for_fdev_updates
|
||||
|
||||
sys.path.append(config.internal_plugin_dir)
|
||||
# This import must be after the sys.path.append.
|
||||
@ -65,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, \
|
||||
@ -162,7 +162,9 @@ def main(): # noqa: C901, CCR001
|
||||
updater = Updater()
|
||||
newversion: EDMCVersion | None = updater.check_appcast()
|
||||
if newversion:
|
||||
print(f'{appversion()} ({newversion.title!r} is available)')
|
||||
# LANG: Update Available Text
|
||||
newverstr: str = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title)
|
||||
print(f'{appversion()} ({newverstr})')
|
||||
else:
|
||||
print(appversion())
|
||||
return
|
||||
@ -495,6 +497,10 @@ def main(): # noqa: C901, CCR001
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
try:
|
||||
check_for_fdev_updates(silent=True)
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Ctrl+C Detected, Attempting Clean Shutdown")
|
||||
logger.debug('Exiting')
|
||||
sys.exit(EXIT_SUCCESS)
|
||||
|
206
EDMCSystemProfiler.py
Normal file
@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
EDMCSystemProfiler.py - GUI or Command-Line Tool to Print Diagnostic Information about EDMC.
|
||||
|
||||
Copyright (c) EDCD, All Rights Reserved
|
||||
Licensed under the GNU General Public License.
|
||||
See LICENSE file.
|
||||
"""
|
||||
import argparse
|
||||
import locale
|
||||
import webbrowser
|
||||
import platform
|
||||
import sys
|
||||
from os import chdir, environ, path
|
||||
import pathlib
|
||||
import logging
|
||||
from journal_lock import JournalLock
|
||||
|
||||
if getattr(sys, "frozen", False):
|
||||
# Under py2exe sys.path[0] is the executable name
|
||||
if sys.platform == "win32":
|
||||
chdir(path.dirname(sys.path[0]))
|
||||
# Allow executable to be invoked from any cwd
|
||||
environ["TCL_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tcl")
|
||||
environ["TK_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tk")
|
||||
|
||||
else:
|
||||
# We still want to *try* to have CWD be where the main script is, even if
|
||||
# not frozen.
|
||||
chdir(pathlib.Path(__file__).parent)
|
||||
|
||||
import config
|
||||
from config import appversion, appname
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox
|
||||
from monitor import monitor
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
|
||||
def get_sys_report(config: config.AbstractConfig) -> str:
|
||||
"""Gather system information about Elite, the Host Computer, and EDMC."""
|
||||
# Calculate Requested Information
|
||||
plt = platform.uname()
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
lcl = locale.getlocale()
|
||||
monitor.currentdir = config.get_str(
|
||||
"journaldir", default=config.default_journal_dir
|
||||
)
|
||||
if not monitor.currentdir:
|
||||
monitor.currentdir = config.default_journal_dir
|
||||
try:
|
||||
logfile = monitor.journal_newest_filename(monitor.currentdir)
|
||||
if logfile is None:
|
||||
raise ValueError("None from monitor.journal_newest_filename")
|
||||
|
||||
with open(logfile, "rb", 0) as loghandle:
|
||||
for line in loghandle:
|
||||
try:
|
||||
monitor.parse_entry(line)
|
||||
except Exception as e:
|
||||
exception_type = e.__class__.__name__
|
||||
monitor.state["GameVersion"] = (
|
||||
exception_type
|
||||
if not monitor.state["GameVersion"]
|
||||
else monitor.state["GameVersion"]
|
||||
)
|
||||
monitor.state["GameBuild"] = (
|
||||
exception_type
|
||||
if not monitor.state["GameBuild"]
|
||||
else monitor.state["GameBuild"]
|
||||
)
|
||||
monitor.state["Odyssey"] = (
|
||||
exception_type
|
||||
if not monitor.state["Odyssey"]
|
||||
else monitor.state["Odyssey"]
|
||||
)
|
||||
except Exception as e:
|
||||
exception_type = e.__class__.__name__
|
||||
monitor.state["GameVersion"] = exception_type
|
||||
monitor.state["GameBuild"] = exception_type
|
||||
monitor.state["Odyssey"] = exception_type
|
||||
|
||||
journal_lock = JournalLock()
|
||||
lockable = journal_lock.open_journal_dir_lockfile()
|
||||
|
||||
report = f"EDMC Version: \n - {appversion()}\n\n"
|
||||
report += "OS Details:\n"
|
||||
report += f"- Operating System: {plt.system} {plt.release}\n"
|
||||
report += f"- Version: {plt.version}\n"
|
||||
report += f"- Machine: {plt.machine}\n"
|
||||
report += f"- Python Version: {platform.python_version()}\n"
|
||||
report += "\nEnvironment Details\n"
|
||||
report += f"- Detected Locale: {lcl[0]}\n"
|
||||
report += f"- Detected Encoding: {lcl[1]}\n"
|
||||
report += f"- Journal Directory: {monitor.currentdir}\n"
|
||||
report += f"- Game Version: {monitor.state['GameVersion']}\n"
|
||||
report += f"- Game Build: {monitor.state['GameBuild']}\n"
|
||||
report += f"- Using Odyssey: {monitor.state['Odyssey']}\n"
|
||||
report += f"- Journal Dir Lockable: {lockable}\n"
|
||||
return report
|
||||
|
||||
|
||||
def copy_sys_report(root: tk.Tk, report: str) -> None:
|
||||
"""Copy the system info to the keyboard."""
|
||||
root.clipboard_clear()
|
||||
root.clipboard_append(report)
|
||||
messagebox.showinfo("System Profiler", "System Report copied to Clipboard", parent=root)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Entry Point for the System Profiler."""
|
||||
# Now Let's Begin
|
||||
root: tk.Tk = tk.Tk()
|
||||
root.withdraw() # Hide the window initially to calculate the dimensions
|
||||
try:
|
||||
icon_image = tk.PhotoImage(
|
||||
file=path.join(cur_config.respath_path, "io.edcd.EDMarketConnector.png")
|
||||
)
|
||||
|
||||
root.iconphoto(True, icon_image)
|
||||
except tk.TclError:
|
||||
root.iconbitmap(path.join(cur_config.respath_path, "EDMarketConnector.ico"))
|
||||
|
||||
sys_report = get_sys_report(cur_config)
|
||||
|
||||
# Set up styling
|
||||
style = ttk.Style(root)
|
||||
style.configure("Title.TLabel", font=("Helvetica", 10, "bold"), foreground="#333")
|
||||
style.configure("Subtitle.TLabel", font=("Helvetica", 8), foreground="#555")
|
||||
style.configure("Details.TLabel", font=("Helvetica", 8), foreground="#222")
|
||||
|
||||
# Build UI
|
||||
title_lbl = ttk.Label(
|
||||
root, text="EDMarketConnector System Profiler", style="Title.TLabel"
|
||||
)
|
||||
title_lbl.grid(row=0, column=0, padx=20, pady=10)
|
||||
|
||||
system_details_lbl = ttk.Label(
|
||||
root, text="System Details:", style="Subtitle.TLabel"
|
||||
)
|
||||
system_details_lbl.grid(row=1, column=0, padx=20, pady=0, sticky="w")
|
||||
|
||||
details_lbl = ttk.Label(
|
||||
root, text=sys_report, style="Details.TLabel", justify="left"
|
||||
)
|
||||
details_lbl.grid(row=2, column=0, padx=20, pady=5, sticky="w")
|
||||
|
||||
# Buttons
|
||||
sys_report_btn = ttk.Button(
|
||||
root,
|
||||
text="Copy System Report",
|
||||
command=lambda: copy_sys_report(root, sys_report),
|
||||
)
|
||||
sys_report_btn.grid(row=3, column=0, padx=20, pady=10, sticky="w")
|
||||
|
||||
github_btn = ttk.Button(
|
||||
root,
|
||||
text="Open GitHub Bug Report",
|
||||
command=lambda: webbrowser.open(
|
||||
"https://github.com/EDCD/EDMarketConnector/issues/new?assignees="
|
||||
"&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title="
|
||||
),
|
||||
)
|
||||
github_btn.grid(row=3, column=0, padx=20, pady=10, sticky="e")
|
||||
|
||||
# Update and get window dimensions
|
||||
root.update()
|
||||
width = root.winfo_reqwidth() + 20
|
||||
height = root.winfo_reqheight() + 20
|
||||
|
||||
# Set window size and show
|
||||
root.geometry(f"{width}x{height}")
|
||||
root.title("EDMarketConnector")
|
||||
root.deiconify()
|
||||
root.resizable(False, False)
|
||||
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Args: Only work if not frozen
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=appname,
|
||||
description="Prints diagnostic and debugging information about the current EDMC configuration.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-console",
|
||||
help="write the system information to the console",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Suppress Logger
|
||||
logger = get_main_logger()
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
if getattr(sys, "frozen", False):
|
||||
sys.stderr._error = "inhibit log creation" # type: ignore
|
||||
|
||||
cur_config = config.get_config()
|
||||
if args.out_console:
|
||||
sys_report = get_sys_report(cur_config)
|
||||
print(sys_report)
|
||||
sys.exit(0)
|
||||
|
||||
main()
|
1
FDevIDs
@ -1 +0,0 @@
|
||||
Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2
|
@ -61,12 +61,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Upravit";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Zobrazit";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Okno";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Nápověda";
|
||||
|
||||
@ -256,9 +250,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Chyba: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Nastavení";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Vyberte, která data chcete ukládat";
|
||||
|
||||
@ -277,9 +268,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Cesta k souboru";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Změnit...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Procházet...";
|
||||
|
||||
@ -289,21 +277,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "E:D umístění souboru deníku";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Klávesová zkratka";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Klávesová zkratka";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Restartujte {APP} pro aktivaci klávesových zkratek";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} vyžaduje oprávnění pro použití klávesových zkratek";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Otevřít Předvolby systému";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Pouze pokud je Elite: Dangerous aktivní";
|
||||
|
||||
|
@ -1,3 +1,15 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Fehler im System Profiler";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Carrier-Daten unvollständig";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Keine Carrier-Daten erhalten";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Carrier-CAPI-Anfragen aktivieren";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Sende Fluglog und CMDR-Status an EDSM";
|
||||
|
||||
@ -36,6 +48,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Fehler: konnte Token nicht erhalten";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC ist in einen unwiederbringlichen kritischen Fehler gelaufen. EDMC fährt zum Selbstschutz herunter!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Das hat EDMC erkannt:\n\n{ERR}\n\nMöchtest Du einen Bug Report auf GitHub einreichen?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI offline wegen Wartung";
|
||||
|
||||
@ -78,12 +96,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Bearbeiten";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Ansicht";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Fenster";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Hilfe";
|
||||
|
||||
@ -93,6 +105,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Auf Aktualisierungen überprüfen...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Öffne System Profiler";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Speichere Originaldaten...";
|
||||
|
||||
@ -123,6 +138,15 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Kopieren";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Ausschneiden";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Einfügen";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Alles auswählen";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "CAPI auth durch Killswitch deaktiviert";
|
||||
|
||||
@ -165,12 +189,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI Fleet Carrier durch Killswitch deaktiviert";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Keine Fleet Carrier-Daten erhalten";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Fleet Carrier-Daten unvollständig";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Keine Kommandanten-Daten erhalten";
|
||||
|
||||
@ -213,12 +237,6 @@
|
||||
/* 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." = "Eins oder mehr deiner aktivierten Plugins hat noch keinen Support für Python 3.x. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Du solltest prüfen, ob es für diese Updates gibt und ansonsten dem Entwickler Bescheid geben, dass sie ihren Code für Python 3.x aktualisieren müssen.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst.";
|
||||
|
||||
/* 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?" = "FDevID Dateien nicht gefunden! Einige Funktionen in Bezug auf Waren sind möglicherweise deaktiviert.\n\nMöchten du die Wiki-Seite zum Einrichten von Submodulen öffnen?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Warendateien fehlen";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Plugins";
|
||||
|
||||
@ -351,9 +369,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Fehler: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Einstellungen";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Bitte wähle zu speichernde Daten aus";
|
||||
|
||||
@ -372,9 +387,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Speicherort";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Ordner...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Durchsuchen...";
|
||||
|
||||
@ -387,24 +399,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI-Einstellungen";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Fleet Carrier CAPI-Anfragen aktivieren";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Makro";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Hotkey";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Starte {APP} neu, um den Tastenkürzel nutzen zu können";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} benötigt für Tastenkürzel Systemrechte (Bedienungshilfen)";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Öffne Systemeinstellungen";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Nur wenn Elite: Dangerous geöffnet ist";
|
||||
|
||||
@ -782,3 +782,22 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} ist verfügbar";
|
||||
|
||||
/* prefs.py: Stable Version of EDMC; */
|
||||
"Stable" = "Stabil";
|
||||
|
||||
/* prefs.py: Select the Update Track (Beta, Stable); */
|
||||
"Update Track" = "Update-Kanal";
|
||||
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Update-Kanal geändert auf {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update-Kanal von Beta auf Stabil geändert. Du wirst keine weiteren Beta-Updates erhalten. Du wirst bis zum nächsten stabilen Release auf der aktuellen Beta-Version bleiben.\n\nDu kannst manuell zur aktuellen stabilen Version zurückkehren. Um dies zu tun, musst du die aktuelle stabile Version manuell herunterladen und installieren. Beachte, dass dies Fehler verursachen oder gar nicht funktionieren könnte, wenn du von einem größeren Versionssprung mit signifikanten Änderungen downgradest.\n\nMöchtest du GitHub öffnen, um den neuesten Release herunterzuladen?";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Kann keinen non-Text-Inhalt einfügen.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Öffnen in {URL}";
|
||||
|
@ -36,6 +36,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Error: unable to get token";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI down for maintenance";
|
||||
|
||||
@ -78,12 +84,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Edit";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "View";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Window";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Help";
|
||||
|
||||
@ -93,6 +93,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Check for Updates...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Open System Profiler";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Save Raw Data...";
|
||||
|
||||
@ -123,6 +126,15 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Copy";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Cut";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Paste";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Select All";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "CAPI auth disabled by killswitch";
|
||||
|
||||
@ -165,11 +177,11 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier disabled by killswitch";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: No fleetcarrier data returned";
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: No Fleet Carrier data returned";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Fleetcarrier data incomplete";
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Fleet Carrier data incomplete";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: No commander data returned";
|
||||
@ -213,12 +225,6 @@
|
||||
/* 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." = "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.";
|
||||
|
||||
/* 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?" = "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?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Missing Commodity Files";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Plugins";
|
||||
|
||||
@ -240,6 +246,9 @@
|
||||
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||
"EDMC: Default Providers Reset" = "EDMC: Default Providers Reset";
|
||||
|
||||
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||
"Awaiting Full CMDR Login" = "Awaiting Full CMDR Login";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "Journal directory already locked";
|
||||
|
||||
@ -348,9 +357,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Error: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Preferences";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Please choose what data to save";
|
||||
|
||||
@ -369,9 +375,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "File location";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Change...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Browse...";
|
||||
|
||||
@ -385,23 +388,11 @@
|
||||
"CAPI Settings" = "CAPI Settings";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Enable Fleetcarrier CAPI Queries";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Keyboard shortcut";
|
||||
"Enable Fleet Carrier CAPI Queries" = "Enable Fleet Carrier CAPI Queries";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Hotkey";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Re-start {APP} to use shortcuts";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} needs permission to use shortcuts";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Open System Preferences";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Only when Elite: Dangerous is the active app";
|
||||
|
||||
@ -498,6 +489,9 @@
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Disabled Plugins";
|
||||
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Error in System Profiler";
|
||||
|
||||
/* stats.py: Cmdr stats; In files: stats.py:58; */
|
||||
"Balance" = "Balance";
|
||||
|
||||
@ -798,3 +792,24 @@
|
||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||
"Ships" = "Ships";
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} is available";
|
||||
|
||||
/* prefs.py: Stable Version of EDMC; */
|
||||
"Stable" = "Stable";
|
||||
|
||||
/* prefs.py: Select the Update Track (Beta, Stable); */
|
||||
"Update Track" = "Update Track";
|
||||
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Update Track Changed to {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Cannot paste non-text content.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Open in {URL}";
|
||||
|
@ -67,12 +67,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Editar";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Visualización";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Ventana";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Ayuda";
|
||||
|
||||
@ -271,9 +265,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Error: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Preferencias";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Seleccione qué datos quiere guardar";
|
||||
|
||||
@ -292,9 +283,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Localización de archivos";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Cambiar...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Examinar...";
|
||||
|
||||
@ -304,21 +292,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "Localización del archivo de Journal de E:D";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Tecla de acceso directo";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Tecla de acceso directo";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Reinicie {APP} para usar los accesos directos de teclado";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} necesita permisos para usar teclas de acceso directo";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Abrir Preferencias del Sistema";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Solo cuando Elite: Dangerous es la aplicación activa";
|
||||
|
||||
|
@ -61,12 +61,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Muokkaa";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Näytä";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Ikkuna";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Apua";
|
||||
|
||||
@ -229,9 +223,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Inaran virhe: {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Valinnat";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Valitse mitkä tiedot tallennetaan";
|
||||
|
||||
@ -250,9 +241,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Tiedostosijainti";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Muuta...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Selaa...";
|
||||
|
||||
@ -262,21 +250,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "E:D lokikirjan tiedostosijainti";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Näppäimistöoikopolku";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Pikanäppäin";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Käynnistä {APP} uudelleen käyttääksesi pikanäppäimiä";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} tarvitsee lupasi pikakuvakkeiden käyttämiseen";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Avaa järjestelmän asetukset";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Vain jos Elite:Dangerous on käynnissä";
|
||||
|
||||
|
@ -78,12 +78,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Édition";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Présentation";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Fenêtre";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Aide";
|
||||
|
||||
@ -222,9 +216,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Erreur : Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Préférences";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Veuillez choisir les données à sauvegarder";
|
||||
|
||||
@ -243,9 +234,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Emplacement des fichiers";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Spécifier...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Parcourir...";
|
||||
|
||||
@ -255,21 +243,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "Emplacement du journal E:D";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Raccourci clavier";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Raccourci clavier";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Rédémarrez {APP} pour utiliser les raccourcis";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} a besoin de permissions pour utiliser les raccourcis";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Ouvrir Préférences Système";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Seulement quand Elite: Dangerous est l'application active";
|
||||
|
||||
|
@ -37,12 +37,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Szerkeszt";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Nézet";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Ablak";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Segítség";
|
||||
|
||||
@ -169,9 +163,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Hiba:Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Beállítások";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Kérem válassza ki a menteni kivánt adatokat.";
|
||||
|
||||
@ -190,9 +181,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Fálj helye";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Módosítás...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Böngész";
|
||||
|
||||
@ -202,21 +190,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "E:D journal fájl helye";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Billentyűparancsok";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Gyorsgomb";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Újranidítás {APP} parancsikonok használatával";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} engedély szükséges a parancsikon használatához";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Rendszerbeállítások megnyitáss";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Csak ha az Elite Dangerous fut";
|
||||
|
||||
|
@ -2,10 +2,10 @@
|
||||
"Send flight log and CMDR status to EDSM" = "Invia il registro di volo e lo stato del CMDR a EDSM";
|
||||
|
||||
/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
|
||||
"Open Log Folder" = "Cartella Log";
|
||||
"Open Log Folder" = "Apri la Cartella dei Log";
|
||||
|
||||
/* inara.py:Text Inara Show API key; In files: inara.py:305; */
|
||||
"Show API Key" = "Mostra API Key";
|
||||
"Show API Key" = "Mostra l'API Key";
|
||||
/* Language name */
|
||||
"!Language" = "Italiano";
|
||||
|
||||
@ -36,11 +36,17 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Errore: impossibile ottenere il token";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC ha incontrato un errore critico, e non riesce a risolvere. EDMC si spegnerà in via cautelativa!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Ecco cosa EDMC ha rilevato:\n\n{ERR}\n\nVuoi compilare un Rapporto di Bug su GitHub?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "CAPI di Frontier inattivo per manutenzione";
|
||||
|
||||
/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
|
||||
"Frontier CAPI query failure" = "Errore nella query CAPI di Frontier";
|
||||
"Frontier CAPI query failure" = "Query CAPI di Frontier fallita";
|
||||
|
||||
/* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */
|
||||
"Update" = "Recupera";
|
||||
@ -78,12 +84,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Appunti";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Vista";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Finestra";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Guida";
|
||||
|
||||
@ -93,6 +93,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Controlla aggiornamenti...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Apri Profilazione di Sistema";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Salva i Dati Grezzi...";
|
||||
|
||||
@ -165,12 +168,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Fleetcarrier CAPI disabilitato dal killswitch";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Nessun dato della fleetcarrier ricevuto";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Dati della Fleetcarrier incompleti";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nessun dato sul commandante";
|
||||
|
||||
@ -213,12 +216,6 @@
|
||||
/* 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." = "Uno o più dei tuoi plugin non ha ancora il supporto per Python 3.x. Per favore guarda la lista '{PLUGINS}' nella tab a '{FILE}' > '{SETTINGS}'. Dovresti controllare se ci sono aggiornamenti, altrimenti avvisa gli sviluppatori che devono aggiornare il codice a Python 3.x.\n\nPuoi disabilitare il plugin rinominando la cartella aggiungendo '{DISABLED}' alla fine del nome.";
|
||||
|
||||
/* 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?" = "File FDevID non trovati! Alcune funzionalità relative alle commodities potrebbero essere disabilitate.\n\n Vuoi aprire la pagina Wiki su come impostare i sottomoduli?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevID: file delle Commodity mancanti";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Plugins";
|
||||
|
||||
@ -351,9 +348,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Errore: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Preferenze";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Si prega di scegliere che dati salvare";
|
||||
|
||||
@ -372,9 +366,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Percorso del file";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Cambia...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Sfoglia...";
|
||||
|
||||
@ -387,24 +378,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Impostazioni CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Abilita le query CAPI della Fleetcarrier";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Scorciatoia di tastiera";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Hotkey";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Riavvia {APP} per usare le scorciatoie";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} ha bisogno dei permessi per usare le scorciatoie";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Apri Preferenze di Sistema";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Solo quando “Elite: Dangerous” è in primo piano";
|
||||
|
||||
@ -803,3 +782,9 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} è disponibile";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Non si può incollare contenuto non testuale";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Apri {URL}";
|
||||
|
@ -78,12 +78,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "編集";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "表示";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "ウィンドウ";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "ヘルプ";
|
||||
|
||||
@ -165,12 +159,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPIフリートキャリアはkillswitchによって無効にされています";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: フリートキャリアデータの返信なし";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: フリートキャリアデータが不完全";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: コマンダーのデータが返ってきませんでした";
|
||||
|
||||
@ -213,12 +207,6 @@
|
||||
/* 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." = "有効にしている1つ以上のプラグインがPython 3.xをサポートしていません。'{FILE}' > '{SETTINGS}'\nメニューで表示される設定ダイアログの'{PLUGINS}' タブの一覧を確認してください。更新済みのバージョンがあるかを確認し、なければ開発者にPython 3.xに対応するように開発者に連絡してください。\n\nプラグインを無効にするにはフォルダ名の最後に'{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?" = "FDevIDファイルが見つかりません!商品に関するいくつかの機能が無効になるかもしれません。\n\nサブモジュールの設定方法についてのWikiページを開きますか?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: 商品ファイルが見つかりません";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "プラグイン";
|
||||
|
||||
@ -351,9 +339,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "エラー: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "環境設定";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "保存するデータの種類を選択して下さい";
|
||||
|
||||
@ -372,9 +357,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "ファイルの出力先";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "変更...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "参照...";
|
||||
|
||||
@ -387,24 +369,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI設定";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "フリートキャリアCAPIクエリを有効にする";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "ショートカット";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "ホットキー";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "ショートカットを利用するには {APP} を再起動してください";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} がショートカットを利用するには許可が必要です";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "システム環境設定を開く";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Elite: Dangerousがアクティブの時だけ有効";
|
||||
|
||||
@ -803,3 +773,4 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} があります";
|
||||
|
||||
|
@ -70,12 +70,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "편집";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "보기";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "창";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "도움말";
|
||||
|
||||
@ -289,9 +283,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "오류: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "설정";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "어느 데이터를 저장할 지 선택해주세요";
|
||||
|
||||
@ -310,9 +301,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "파일 위치";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "바꾸기...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "찾아보기...";
|
||||
|
||||
@ -322,21 +310,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "E:D 저널(journal) 파일 위치";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "단축키 설정";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "단축키";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "단축키를 사용하려면 {APP}을(를) 다시 시작하십시오";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "단축기 사용을 위해선 {APP}이 권한 허용을 필요로 합니다";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "시스템 설정 열기";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Elite: Dangerous가 활성 창 상태일때만";
|
||||
|
||||
|
@ -34,12 +34,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Labot";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Skats";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Logs";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Palīdzība";
|
||||
|
||||
@ -157,9 +151,6 @@
|
||||
/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */
|
||||
"Error: Can't connect to EDSM" = "Kļūda: Nevar savienoties ar EDSM";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Iestatījumi";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Lūdzu izvēlaties kādus datus saglabāt";
|
||||
|
||||
@ -178,9 +169,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Faila atrašanās vieta";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Mainīt...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Pārlūkot...";
|
||||
|
||||
@ -190,21 +178,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "E:D žurnāla faila atrašanās vieta";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Ātrais taustiņš";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Ātrais taustiņš";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Restartējiet {APP} lai izmantotu īssceļus";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} vajag atļaujas lai izmantotu īssceļus";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Atvērt sistēmas iestatījumus";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Tikai kad Elite: Dangerous ir aktīvā aplikācija";
|
||||
|
||||
|
152
L10n/nl.strings
@ -1,3 +1,14 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Fout in systeemprofiler";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Stuur logboek en CMDR-status naar EDSM";
|
||||
|
||||
/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
|
||||
"Open Log Folder" = "Open logboekmap";
|
||||
|
||||
/* inara.py:Text Inara Show API key; In files: inara.py:305; */
|
||||
"Show API Key" = "Toon API key";
|
||||
/* Language name */
|
||||
"!Language" = "Nederlands";
|
||||
|
||||
@ -10,12 +21,33 @@
|
||||
/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
|
||||
"Error: Wrong Cmdr" = "Fout: Verkeerde Cmdr";
|
||||
|
||||
/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
|
||||
"Error" = "Fout";
|
||||
|
||||
/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */
|
||||
"Error: customer_id doesn't match!" = "Fout: customer_id komt niet overeen!";
|
||||
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Fout: kon token niet verkrijgen";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "Er trad een kritieke fout op, en EDMC kan niet herstellen. Het programma sluit af uit zelfbescherming!";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI offline voor onderhoud";
|
||||
|
||||
/* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */
|
||||
"Update" = "Bijwerken";
|
||||
|
||||
/* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */
|
||||
"Always on top" = "Altijd op voorgrond";
|
||||
|
||||
/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
|
||||
"Unknown" = "Onbekend";
|
||||
|
||||
/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
|
||||
"Error: Check E:D journal file location" = "Fout: Controleer locatie van E:D logboekbestand";
|
||||
|
||||
/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */
|
||||
"Cmdr" = "Cmdr";
|
||||
|
||||
@ -37,12 +69,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Bewerken";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Beeld";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Venster";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Help";
|
||||
|
||||
@ -52,6 +78,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Controleren op updates...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Open systeemprofiler";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Sla onbewerkte gegevens op...";
|
||||
|
||||
@ -61,6 +90,12 @@
|
||||
/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
|
||||
"Documentation" = "Documentatie";
|
||||
|
||||
/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
|
||||
"Troubleshooting" = "Probleemoplossing";
|
||||
|
||||
/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
|
||||
"Report A Bug" = "Meld een bug";
|
||||
|
||||
/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
|
||||
"Privacy Policy" = "Privacybeleid";
|
||||
|
||||
@ -76,6 +111,18 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Kopieëren";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Knippen";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Plakken";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Selecteer alles";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "CAPI authenticatie uitgeschakeld door killswitch";
|
||||
|
||||
/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */
|
||||
"Logging in..." = "Bezig met inloggen...";
|
||||
|
||||
@ -91,9 +138,39 @@
|
||||
/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */
|
||||
"Station doesn't have a market!" = "Station heeft geen markt!";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */
|
||||
"CAPI query aborted: Cmdr name unknown" = "CAPI query onderbroken: Cmdr naam onbekend";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */
|
||||
"CAPI query aborted: Game mode unknown" = "CAPI query onderbroken: game mode onbekend";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */
|
||||
"CAPI query aborted: GameVersion unknown" = "CAPI query onderbroken: GameVersion onbekend";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */
|
||||
"CAPI query aborted: Current system unknown" = "CAPI query onderbroken: huidig systeem onbekend";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */
|
||||
"CAPI query aborted: In other-ship multi-crew" = "CAPI query onderbroken: in multi-crew ander schip";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */
|
||||
"CAPI query aborted: CQC (Arena) detected" = "CAPI query onderbroken: in CQC (Arena)";
|
||||
|
||||
/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */
|
||||
"Fetching data..." = "Data wordt opgehaald...";
|
||||
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier uitgeschakeld door killswitch";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: geen fleet carrier data verkregen";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: data fleet carrier incompleet";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: geen commander data verkregen";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1246; stats.py:333; */
|
||||
"Who are you?!" = "Wie ben je?!";
|
||||
|
||||
@ -103,6 +180,12 @@
|
||||
/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */
|
||||
"What are you flying?!" = "Waar vlieg je in?!";
|
||||
|
||||
/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */
|
||||
"Frontier CAPI server error" = "Frontier CAPI serverfout";
|
||||
|
||||
/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */
|
||||
"CAPI: Refreshing access token..." = "CAPI: toegangstoken vernieuwen...";
|
||||
|
||||
/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */
|
||||
"Last updated at %H:%M:%S" = "Voor het laatst bijgewerkt om %H:%M:%S";
|
||||
|
||||
@ -121,6 +204,9 @@
|
||||
/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1864; prefs.py:292; */
|
||||
"OK" = "OK";
|
||||
|
||||
/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1936; */
|
||||
"Shutting down..." = "Afsluiten...";
|
||||
|
||||
/* 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." = "Een of meer ingeschakelde plugins hebben nog geen Python 3.x ondersteuning. Kijk op de lijst op de '{PLUGINS}' tab van '{FILE}' > '{SETTINGS}'. Check of er een bijgewerkte versie beschikbaar is. Laat anders de developer weten dat ze de code moeten updaten voor Python 3.x.\n\nJe kan de plugin uitschakelen door het mapje te hernoemen met '{DISABLED}' aan het eind van de naam.";
|
||||
|
||||
@ -130,9 +216,45 @@
|
||||
/* 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" = "EDMS: Plugins zonder Python 3.x ondersteuning";
|
||||
|
||||
/* 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." = "Een of meer van je ingeschakelde plugins kon niet laden. Controleer de lijst op de '{PLUGINS}' tab van '{FILE}' > '{SETTINGS}'. Dit zou veroorzaakt kunnen worden door een verkeerde mappenstructuur. Het bestand load.py hoort op plugins/PLUGIN_NAAM/load.py\n\nJe kunt een plugin uitschakelen door '{DISABLED}' aan het einde van de naam van de plugin-map toe te voegen.";
|
||||
|
||||
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||
"Awaiting Full CMDR Login" = "Wachten op volledige CMDR login";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "Logboekmap al vergrendeld";
|
||||
|
||||
/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:225:226; */
|
||||
"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "De nieuwe logboekmaplocatie is al vergrendeld. {CR} Je kan dit probleem verhelpen en het dan opnieuw proberen, of dit probleem negeren.";
|
||||
|
||||
/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:230; */
|
||||
"Retry" = "Probeer opnieuw";
|
||||
|
||||
/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:234; */
|
||||
"Ignore" = "Negeer";
|
||||
|
||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||
"Default" = "Standaard";
|
||||
|
||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||
"Auto" = "Auto";
|
||||
|
||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||
"Normal" = "Normaal";
|
||||
|
||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||
"Beta" = "Beta";
|
||||
|
||||
/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */
|
||||
"Normal URL" = "Normale URL";
|
||||
|
||||
/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */
|
||||
"Reset" = "Reset";
|
||||
|
||||
/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */
|
||||
"Beta URL" = "Beta-URL";
|
||||
|
||||
/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */
|
||||
"Error: Can't connect to EDDN" = "Fout: Kan geen verbinding maken met EDDN";
|
||||
|
||||
@ -175,9 +297,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Fout: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Preferences";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Welke gegevens wilt u opslaan?";
|
||||
|
||||
@ -196,9 +315,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Bestand locatie";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Wijzigen...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Bladeren...";
|
||||
|
||||
@ -208,21 +324,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "E:D journaal bestand locatie";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Toetscombinaties";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Hotkey";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "{APP} heropstarten om toetscombinaties te gebruiken";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} heeft toestemming nodig om toetscombinaties te gebruiken";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Open Systeemvoorkeuren";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Alleen wanneer Elite: Dangerous de actieve applicatie is";
|
||||
|
||||
|
@ -78,12 +78,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Edycja";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Widok";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Okno";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Pomoc";
|
||||
|
||||
@ -165,12 +159,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Fleetcarier CAPI wyłączony \"kill switchem\"";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Nie zwrócono danych dotyczących lotniskowca";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Niekompletne dane lotniskowca";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nie zwrócono danych dowódcy";
|
||||
|
||||
@ -213,12 +207,6 @@
|
||||
/* 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";
|
||||
|
||||
@ -351,9 +339,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Błąd: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Preferencje";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Jakie dane zapisywać";
|
||||
|
||||
@ -372,9 +357,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Położenie pliku";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Zmień...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Przeglądaj...";
|
||||
|
||||
@ -387,24 +369,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Ustawienia CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Włącz zapytania CAPI dla lotniskowca";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Skrót klawiaturowy";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Skr. Klaw.";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Zrestartuj {APP} by użyć{CR}skrótu klawiszowego.";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} wymaga uprawnień by{CR}móc korzystać ze skrótów klawiszowych.";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Otwórz Preferencje systemowe";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Tylko gdy Elite: Dangerous jest aplikacją aktywną";
|
||||
|
||||
@ -803,3 +773,4 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "Dostępna nowa wersja {NEWVER}";
|
||||
|
||||
|
@ -1,3 +1,15 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Erro no Perfil de Sistema";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Dados de porta-frotas incompletos";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Nenhum dado de porta-frotas retornado";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Enviar registro de voo e status do CMDT para o EDSM";
|
||||
|
||||
@ -36,6 +48,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Erro: não foi possível obter o token";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC encontrou um erro crítico e não conseguiu recuperar-se. Encerrando o EDMC para proteção própria!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Aqui está o que o EDMC detectou:\n\n{ERR}\n\nGostaria de reportar o bug em nosso GitHub?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "CAPI da Frontier indisponível por manutenção";
|
||||
|
||||
@ -78,12 +96,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Editar";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Visualizar";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Janela";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Ajuda";
|
||||
|
||||
@ -93,6 +105,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Verificar por Atualizações...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Abrir Perfil de Sistema";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Salvar dados brutos...";
|
||||
|
||||
@ -123,6 +138,15 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Copiar";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Recortar";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Colar";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Selecionar tudo";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "Autenticação CAPI desativada pelo botão de interrupção.";
|
||||
|
||||
@ -165,12 +189,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI para porta-frotas desativado pelo botão de interrupção.";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Nenhum dado de porta-fortas retornado";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Dados de porta-frota incompletos";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nenhum dado de comandante retornado";
|
||||
|
||||
@ -213,12 +237,6 @@
|
||||
/* 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";
|
||||
|
||||
@ -351,9 +369,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Erro: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Preferências";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Por favor, escolha quais dados para salvar";
|
||||
|
||||
@ -372,9 +387,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Localização do Arquivo";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Alterar...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Procurar...";
|
||||
|
||||
@ -387,24 +399,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Configurações de CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Atalho de teclado";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Tecla de atalho";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Reinicie o {APP} para usar os atalhos";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} precisa de permissão para usar atalhos";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Abrir preferências do sistema";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Somente quando o Elite: Dangerous é o aplicativo ativo";
|
||||
|
||||
@ -803,3 +803,22 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} está disponível";
|
||||
|
||||
/* prefs.py: Stable Version of EDMC; */
|
||||
"Stable" = "Estável";
|
||||
|
||||
/* prefs.py: Select the Update Track (Beta, Stable); */
|
||||
"Update Track" = "Caminho de Atualizações";
|
||||
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Caminho de Atualizações modificado para {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Caminho de atualizações modificado de Estável para Beta. Você não receberá mais atualizações Beta. Você seguirá na versão Beta atual até a próxima atualização Estável.\n\nVocê pode reverter manualmente para a última versão Estável. Para fazer isso, faça download e instale a última versão Estável manualmente. Fique atento: isto pode introduzir bugs ou quebrar completamente a aplicação caso você o retorno volte para uma versão principal com mudanças significativas.\n\nGostaria de abrir o GitHub para fazer download da última versão?";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Só é possível colar texto.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Abrir em {URL}";
|
||||
|
@ -36,6 +36,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Erro: Não é possível obter o token";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "O EDMC encontrou um erro crítico e não consegue recuperar. O EDMC irá desligar-se para proteção própria.";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "O EDMC detectou o seguinte:\n\n{ERR}\n\nDeseja preencher um Bug Report no GitHub?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "CAPI da Frontier em manutenção.";
|
||||
|
||||
@ -78,12 +84,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Editar";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Ver";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Janela";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Ajuda";
|
||||
|
||||
@ -93,6 +93,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Procurar por Actualizações...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Abrir Informações do Sistema";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Salvar dados em bruto...";
|
||||
|
||||
@ -165,12 +168,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Consulta à CAPI de Transportador de Frota cancelada por botão";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Sem dados de Transportador de Frota";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Dados de Transportador de Frota incompletos";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Dados do Comandante não recebidos";
|
||||
|
||||
@ -213,12 +216,6 @@
|
||||
/* 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 activados não possui ainda suporte para Python 3.x. Por favor verifique a lista na aba '{PLUGINS}' em '{FILE}' > '{SETTINGS}'. Deverá verificar se está disponível uma versão mais actualizada, caso contrário, informe o desenvolvedor de que necessita de actualizar o código para suportar Python 3.x.\n\nPode desactivar um plugin colocando '{DISABLED}' no fim do nome da pasta correspondente.";
|
||||
|
||||
/* 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?" = "Ficheiros FDevID não encontrados. Algumas funcionalidades relacionadas com mercadorias poderão estar indisponíveis.\nDeseja abrir a página da Wiki sobre configuração de submódulos?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Ficheiros de Mercadorias em falta.";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Plugins";
|
||||
|
||||
@ -231,6 +228,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." = "Um ou mais dos plugins activados não arrancou correctamente. Por favor consule a lista na aba {PLUGINS}', em '{FILE}' > '{SETTINGS}'. Isto poderá ser causado por má estrutura de pastas. O ficheiro load.py deverá estar dentro de plugins/NOME_DO_PLUGIN/load.py. \nPode desactivar um plugin colocando '{DISABLED}' no fim do nome da pasta.";
|
||||
|
||||
/* 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 vários dos Provedores de URLs eram inválidos e 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} era {OLDPROV}, foi reconfigurado para {NEWPROV}";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||
"EDMC: Default Providers Reset" = "EDMC: Provedores for defeito reconfigurados.";
|
||||
|
||||
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||
"Awaiting Full CMDR Login" = "Aguardando pelo login completo do CMDR.";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "Directório do Diário já se encontra bloqueado";
|
||||
|
||||
@ -339,9 +348,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Erro: Inara: {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Preferências";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Por favor escolher quais os dados a guardar";
|
||||
|
||||
@ -360,9 +366,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Localização do Ficheiro";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Alterar...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Procurar...";
|
||||
|
||||
@ -375,24 +378,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Definições CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Ligar Consultas CAPI de Transportador de Frota";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Atalho de Teclado";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Tecla Rápida";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Reinicie o {APP} para usar os atalhos";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "A {APP} precisa de autorização para usar atalhos";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Abrir Preferências do Sistema";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Apenas quando o Elite:Dangerous é a aplicação activa";
|
||||
|
||||
@ -789,3 +780,11 @@
|
||||
/* 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} disponível.";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Só é possível colar conteúdo texto.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Abrir em {URL}";
|
||||
|
@ -36,6 +36,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Ошибка: не удается получить токен";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC столкнулся с критической ошибкой и не может восстановиться. EDMC отключается для собственной защиты!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Вот что обнаружил EDMC:\n\n{ERR}\n\nВы хотите отправить сообщение об ошибке на GitHub?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI остановлен для технического обслуживания";
|
||||
|
||||
@ -78,12 +84,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Правка";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Вид";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Окно";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Дополнительно";
|
||||
|
||||
@ -93,6 +93,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Проверить наличие обновлений...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Открыть системный профайлер";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Сохранить \"сырые\" данные..";
|
||||
|
||||
@ -123,6 +126,15 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Копировать";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Вырезать";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Вставить";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Выбрать все";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "CAPI аутентификация отключена с помощью killswitch";
|
||||
|
||||
@ -165,12 +177,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI кораблей-носителей отключен с помощью killswitch";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Нет данных о флотоносце";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Неполная информация о флотоносце";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Нет данных пилота";
|
||||
|
||||
@ -213,12 +225,6 @@
|
||||
/* 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." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{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?" = "FDevID файлы не найдены! Некоторые функции, связанные с товарами, могут быть отключены.\n\nВы хотите открыть страницу Wiki о том, как настроить вспомогательные модули?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: отсутствуют файлы товаров";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Плагины";
|
||||
|
||||
@ -351,9 +357,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Ошибка: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Настройки";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Выберите какие данные стоит сохранять";
|
||||
|
||||
@ -372,9 +375,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Путь хранения файлов";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Обзор...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Просмотреть...";
|
||||
|
||||
@ -387,24 +387,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Настройки CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Включить запросы к CAPI о флотоносце";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Сочетание клавиш";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Горячая клавиша";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Перезапустите {APP}, чтобы использовать сочетание клавиш";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} требуется разрешение на использование глобальных сочетаний клавиш";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Открыты «Настройки системы»";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Только когда окно Elite: Dangerous активно";
|
||||
|
||||
@ -803,3 +791,22 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} доступен";
|
||||
|
||||
/* prefs.py: Stable Version of EDMC; */
|
||||
"Stable" = "Стабильная";
|
||||
|
||||
/* prefs.py: Select the Update Track (Beta, Stable); */
|
||||
"Update Track" = "Обновить маршрут";
|
||||
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Обновить маршрут. Изменено на {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Изменен путь обновления с \"Бета\" на \"Стабильный\". Вы больше не будете получать обновления \"Бета\". Вы останетесь на текущей бета-версии до следующего стабильного релиза.\n\nВы можете вручную вернуться к последней версии \"Стабильная\". Для этого необходимо загрузить и установить последнюю версию \"Стабильная\" вручную. Обратите внимание, что при переходе между основными версиями со значительными изменениями могут возникнуть ошибки или полная поломка.\n\nХотите открыть GitHub, чтобы загрузить последнюю версию?";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Невозможно добавить нетекстовое содержимое.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Открыть через {URL}";
|
||||
|
@ -31,12 +31,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Uredi";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Pogled";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Okno";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Pomoč";
|
||||
|
||||
@ -136,9 +130,6 @@
|
||||
/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */
|
||||
"Error: Can't connect to EDSM" = "Napaka: Povezava z EDSM ni mogoča";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Nastavitve";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Izberite podatke, ki jih želite shraniti";
|
||||
|
||||
@ -154,30 +145,15 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Lokacija datoteke";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Spremeni...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Prebrskaj...";
|
||||
|
||||
/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:405; */
|
||||
"Output" = "Izpis";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Bližnjica";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Bližnjica";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Ponovno poženi {APP} za uporabo bližnjic";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} potrebuje dovoljenja za uporabo bližnjic";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Odpri sistemske nastavitve";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Samo ko je Elite:Dangerous aktivna aplikacija ";
|
||||
|
||||
|
@ -1,5 +1,17 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Greša u System Profileru";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Fleet Carrier podaci nisu kompletni";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Fleet Carrier nisu dobijeni";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Omogući Fleet Carrier CAPI upite";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Pošalji log leta i Cmdr status na EDSM";
|
||||
"Send flight log and CMDR status to EDSM" = "Pošalji log leta i CMDR status na EDSM";
|
||||
|
||||
/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
|
||||
"Open Log Folder" = "Otvori folder sa logovima";
|
||||
@ -22,7 +34,7 @@
|
||||
"Error: Invalid Credentials" = "Greška: Neispravni kredencijali";
|
||||
|
||||
/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
|
||||
"Error: Wrong Cmdr" = "Greška: Pogrešan Cmdr";
|
||||
"Error: Wrong Cmdr" = "Greška: Pogrešan CMDR";
|
||||
|
||||
/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
|
||||
"Error" = "Greška";
|
||||
@ -36,6 +48,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Greška: Nemoguće dobaviti token";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC je naišao na kritičnu grešku i ne može da se oporavi. EDMC će biti ugašen zbog vlastite zaštite.";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "EDMC je detektovao sljedeće:\n\n{ERR}\n\nDa li želite da izvršite Bug Report na GitHub-u?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI nedostupan zbog održavanja";
|
||||
|
||||
@ -55,7 +73,7 @@
|
||||
"Error: Check E:D journal file location" = "Greška: Provjerite lokaciju E:D journal fajla";
|
||||
|
||||
/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */
|
||||
"Cmdr" = "Cmdr";
|
||||
"Cmdr" = "CMDR";
|
||||
|
||||
/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:915; EDMarketConnector.py:1487; */
|
||||
"Role" = "Uloga";
|
||||
@ -78,12 +96,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Uredi";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Pogled";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Prozor";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Pomoć";
|
||||
|
||||
@ -93,6 +105,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Provjeri nadogradnje...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Otvori System Profiler";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Snimi sirove podatke...";
|
||||
|
||||
@ -123,6 +138,15 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Kopiraj";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Izreži";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Nalijepi";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Selektuj sve";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "CAPI autentifikacija onemogućena putem sistemskog prekidača";
|
||||
|
||||
@ -165,12 +189,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier onemogućen putem sistemskog prekidača";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Fleetcarrier podaci nisu dobijeni";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Fleetcarrier podaci nisu potpuni";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nema podataka o komandantu";
|
||||
|
||||
@ -213,12 +237,6 @@
|
||||
/* 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." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Provjerite da li postoji nadograđena verzija ili obavijesite autora da treba da promijeni kod za Python 3.x.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera.";
|
||||
|
||||
/* 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?" = "FDevID fajlovi nisu pronađeni. Određene opcije vezane za robu bi mogle da budu nedostupne.\n\nDa li želite da otvorite Wiki stranicu sa uputstvom za podešavanje podmodula?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Nedostaju Commodity fajlovi";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Dodaci (plugins)";
|
||||
|
||||
@ -231,6 +249,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." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Pogrešna struktura foldera može da uzrokuje problem. Fajl load.py treba da bude smješten u plugins/PLUGIN_NAME/load.py.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera.";
|
||||
|
||||
/* 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" = "Jedan ili više URL Providera su pogrešni i zbog toga su resetovani:\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} je bio postavljen na {OLDPROV}, a sad je resetovan na {NEWPROV}\n";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
|
||||
"EDMC: Default Providers Reset" = "EDMC: Standardni Provideri su resetovani";
|
||||
|
||||
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
|
||||
"Awaiting Full CMDR Login" = "Čekam da se CMDR potpuno uloguje";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "Journal direktorijum je već zaključan";
|
||||
|
||||
@ -325,7 +355,7 @@
|
||||
"Error: Can't connect to EDSM" = "Greška: Nemoguće povezivanje sa EDSM";
|
||||
|
||||
/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:257; */
|
||||
"Send flight log and Cmdr status to Inara" = "Pošalji log leta i Cmdr status na Inara";
|
||||
"Send flight log and Cmdr status to Inara" = "Pošalji log leta i CMDR status na Inara";
|
||||
|
||||
/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */
|
||||
"Inara credentials" = "Kredencijali za Inara";
|
||||
@ -339,9 +369,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Greška: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Podešavanja";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Izaberite koji podaci se snimaju";
|
||||
|
||||
@ -360,9 +387,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Lokacija fajlova";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Promijeni...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Potraži...";
|
||||
|
||||
@ -375,24 +399,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI Podešavanja";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Omogući Fleetcarrier CAPI Upite";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Prečica";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Prečica";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Restartujte {APP} da bi ste koristili prečice";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} traži dozvolu da koristi prečice";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Otvori sistemska podešavanja";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Samo kada je Elite: Dangerous aktivna aplikacija";
|
||||
|
||||
@ -789,3 +801,24 @@
|
||||
/* stats.py: Status dialog title; In files: stats.py:418; */
|
||||
"Ships" = "Brodovi";
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} je dostupna";
|
||||
|
||||
/* prefs.py: Stable Version of EDMC; */
|
||||
"Stable" = "Stabilna";
|
||||
|
||||
/* prefs.py: Select the Update Track (Beta, Stable); */
|
||||
"Update Track" = "Kanal osvježavanja";
|
||||
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Kanal za osvježavanje je promijenjen u {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Kanal za osvježavanje je promijenjen iz Beta u Stabilni. Više nećete primati Beta osvježavanja. Ostaćete na trenutnoj Beta verziji do sljedeće Stabilne verzije.\n\nMožete se ručno vratiti na posljednju Stabilnu verziju. Da biste to učinili morate da skinete i ručno instališete posljednju Stabilnu verziju. Ova procedura može da uvede nove bugove ili da potpuno onesposobi program ako prelazite između verzija sa mnogo značajnih promjena.\n\nDa li želite da otvorite GitHub stranicu za download posljednje verzije?";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Nije moguće da se zalijepi netekstualni sadržaj.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Otvori u {URL}";
|
||||
|
@ -36,6 +36,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Greška: nemoguće dobaviti token";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC je naišao na kritičnu grešku i ne može da se oporavi. EDMC će biti ugašen radi svoje zaštite.";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Evo šta je EDMC detektovao:\n\n{ERR}\n\nDa li želite da napravite Bug Report na GitHub-u?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI isključen radi održavanja";
|
||||
|
||||
@ -78,12 +84,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Izmeni";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Pogled";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Prozor";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Pomoć";
|
||||
|
||||
@ -93,6 +93,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Proveri da li postoje izmene...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Otvori System Profiler";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Snimi sirove podatke...";
|
||||
|
||||
@ -165,12 +168,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI fleetcarrier deaktiviran preko sistemskog prekidača";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Podaci o fleetcarrier-u nisu dobijeni";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Podaci o fleetcarrier-u su nepotpuni";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Nema podataka o komandiru";
|
||||
|
||||
@ -213,12 +216,6 @@
|
||||
/* 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." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Proverite da li postoji nadograđena verzija ili obavesite autora da treba da promeni kod za Python 3.x.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera.";
|
||||
|
||||
/* 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?" = "FDevID fajlovi nisu pronađeni! Neke funkcionalnosti vezane za artikle će možda biti deaktivirane.\n\nDa li želite da otvorite Wiki stranu koja objašnjava kako da podesite pod-module?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Nedostaju fajlovi za robu";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Dodaci (plugins)";
|
||||
|
||||
@ -351,9 +348,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Greška: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Podešavanja";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Izaberite koji se podaci snimaju";
|
||||
|
||||
@ -372,9 +366,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Lokacija fajlova";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Izmeni...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Potraži...";
|
||||
|
||||
@ -387,24 +378,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI Podešavanja";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Aktiviraj Fleetcarrier CAPIU Upite";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Skraćenica";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Skraćenica";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Restartujte {APP} da bi ste koristili skraćenice";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} traži dozvolu da koristi skraćenice";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Otvori sistemska podešavanja";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Samo kada je Elite: Dangerous aktivna aplikacija";
|
||||
|
||||
@ -803,3 +782,9 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} je dostupna";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Nemoguće je nalepiti netekstualni sadržaj.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Otvori na {URL}";
|
||||
|
@ -37,12 +37,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Ändra";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "View";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Fönster";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Hjälp";
|
||||
|
||||
@ -175,9 +169,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Fel: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Inställningar";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Välj vilket data som skall lagras";
|
||||
|
||||
@ -196,9 +187,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Filsökväg";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Ändra...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Bläddra...";
|
||||
|
||||
@ -208,21 +196,9 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "E:D journal-fil sökväg";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Genväg, tangentbord";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Snabbkommando";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Starta om {APP} för att använda genvägar";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} behöver rättigheter för att använda genvägar";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Öppna systeminställningar";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Bara när: Elite Dangerous är det aktiva programmet";
|
||||
|
||||
|
@ -1,3 +1,15 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Sistem Profilcisinde Hata oluştu";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Filo Taşıyıcısı verileri eksik";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Filo Taşıyıcısı verisi bulunamadı";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Filo Taşıyıcı CAPI Sorgularını Etkinleştir";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Uçuş günlüğünü ve CMDR durumunu EDSM'e gönder";
|
||||
|
||||
@ -36,6 +48,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Hata: token erişilemedi";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC kritik bir hatayla karşılaştı ve kurtarılamıyor. EDMC kendi koruması için kapanıyor!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "EDMC tespit edilen hata:\n\n{ERR}\n\nGitHub'da bir Hata Raporu göndermek ister misiniz?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI bakım için kapalı durumda";
|
||||
|
||||
@ -78,12 +96,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Düzenle";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Görüntüle";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Pencere";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Yardım";
|
||||
|
||||
@ -93,6 +105,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Güncellemeleri Denetle...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Sistem Profilcisi'ni açın";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Ham Verileri Kaydet...";
|
||||
|
||||
@ -123,6 +138,15 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Kopyala";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Kes";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Yapıştır";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Tümünü Seç";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "CAPI kimlik doğrulaması killswitch tarafından devre dışı bırakıldı";
|
||||
|
||||
@ -165,12 +189,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI filo taşıyıcısı killswitch tarafından devre dışı bırakıldı";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Filo taşıyıcı verisi gelmedi.";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Filo taşıyıcı verileri eksik";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi";
|
||||
|
||||
@ -213,12 +237,6 @@
|
||||
/* 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." = "Etkin eklentilerinizden bir veya daha fazlası henüz Python 3.x desteğine sahip değil. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Güncellenmiş bir sürümün mevcut olup olmadığını kontrol edin, aksi takdirde geliştiriciyi Python 3.x kodunu güncellemesi gerektiği konusunda uyarmalısınız.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz.";
|
||||
|
||||
/* 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?" = "FDevID Dosyaları bulunamadı! Ürünlerle ilgili bazı işlevler devre dışı bırakılabilir.\n\nAlt modüllerin nasıl kurulacağına ilişkin Wiki sayfasını açmak ister misiniz?";
|
||||
|
||||
/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
|
||||
"FDevIDs: Missing Commodity Files" = "FDevIDs: Eksik Ürün Dosyaları";
|
||||
|
||||
/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
|
||||
"Plugins" = "Eklentiler";
|
||||
|
||||
@ -351,9 +369,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Hata: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Tercihler";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Lütfen hangi verilerin kaydedileceğini seçin";
|
||||
|
||||
@ -372,9 +387,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Dosya konumu";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Değiştir...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Gezin...";
|
||||
|
||||
@ -387,24 +399,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI ayarları";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Klavye Kısayolu";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Kısayoltuşu";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Kısayolları kullanmak için {APP}'i yeniden başlat";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP}'nin kısayolları kullanabilmesi için izne ihtiyacı var";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Sistem Tercihlerini Aç";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Sadece Elite: Dangerous aktif uygulama olduğunda";
|
||||
|
||||
@ -803,3 +803,22 @@
|
||||
|
||||
/* update.py: Update Available Text; In files: update.py:229; */
|
||||
"{NEWVER} is available" = "{NEWVER} sürüm mevcut";
|
||||
|
||||
/* prefs.py: Stable Version of EDMC; */
|
||||
"Stable" = "Stabil";
|
||||
|
||||
/* prefs.py: Select the Update Track (Beta, Stable); */
|
||||
"Update Track" = "Takibi Güncelle";
|
||||
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Aktif Takip {TRACK} olarak güncellendi.";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Güncelleme takibi Beta'dan Kararlı olarak değiştirildi. Artık Beta güncellemelerini almayacaksınız. Bir sonraki Kararlı sürüme kadar mevcut Beta sürümünüzde kalacaksınız.\n\nEn son Stabil sürüme manuel olarak geri dönebilirsiniz. Bunu yapmak için en son Stabil sürümünü manuel olarak indirip yüklemeniz gerekir. Önemli değişiklikler içeren ana sürümler arasında sürüm düşürme durumunda bunun hatalara yol açabileceğini veya tamamen bozulabileceğini unutmayın.\n\nEn son sürümü indirmek için GitHub'u açmak istiyor musunuz?";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Metin dışı içerik yapıştırılamadı";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Şurada aç {URL}";
|
||||
|
297
L10n/uk.strings
@ -1,9 +1,35 @@
|
||||
/* prefs.py: Catch & Record Profiler Errors; */
|
||||
"Error in System Profiler" = "Помилка у Профайлері Систем";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleet Carrier data incomplete" = "CAPI: Дані корабля-носія неповні";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No Fleet Carrier data returned" = "CAPI: Немає даних корабля-носія";
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleet Carrier CAPI Queries" = "Ввімкнути запити CAPI кораблів-носіїв";
|
||||
|
||||
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
|
||||
"Send flight log and CMDR status to EDSM" = "Відправляти дані бортового журналу до EDSM";
|
||||
|
||||
/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
|
||||
"Open Log Folder" = "Відкрити папку журналу";
|
||||
|
||||
/* inara.py:Text Inara Show API key; In files: inara.py:305; */
|
||||
"Show API Key" = "Показати ключ API";
|
||||
/* Language name */
|
||||
"!Language" = "Українська";
|
||||
|
||||
/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */
|
||||
"Error: Frontier CAPI didn't respond" = "Помилка: Frontier CAPI не відповідає";
|
||||
|
||||
/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */
|
||||
"Error: Frontier server is lagging" = "Помилка: З`єднання з сервером гри з затримками!";
|
||||
|
||||
/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */
|
||||
"Docked but unknown station: EDO Settlement?" = "Пристикований, проте станція невідома: поселення EDO?";
|
||||
|
||||
/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */
|
||||
"Error: Invalid Credentials" = "Помилка: Невірні облікові дані!";
|
||||
|
||||
@ -22,6 +48,18 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Помилка: не вдалося отримати токен";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDMC encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDMC виявив критичну помилку, і не може відновити функціонування. EDMC вимкнеться заради самозахисту!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Ось що EDMC зміг з'ясувати:\n\n{ERR}\n\nХочете створити баг репорт на GitHub?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI відключений через технічне обслуговування";
|
||||
|
||||
/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
|
||||
"Frontier CAPI query failure" = "Помилка запиту Frontier CAPI";
|
||||
|
||||
/* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */
|
||||
"Update" = "Оновлення";
|
||||
|
||||
@ -31,6 +69,9 @@
|
||||
/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
|
||||
"Unknown" = "Невідомо";
|
||||
|
||||
/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
|
||||
"Error: Check E:D journal file location" = "Помилка: перевірте розташування файлу журналу E:D";
|
||||
|
||||
/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */
|
||||
"Cmdr" = "Км-др";
|
||||
|
||||
@ -55,12 +96,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "Редагувати";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "Вид";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "Вікно";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "Довідка";
|
||||
|
||||
@ -70,6 +105,9 @@
|
||||
/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
|
||||
"Check for Updates..." = "Перевірка оновлень...";
|
||||
|
||||
/* EDMarketConnector.py: Help > Open System Profiler; In files: EDMarketConnector.py:888; */
|
||||
"Open System Profiler" = "Відкрити Профайлер Систем";
|
||||
|
||||
/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
|
||||
"Save Raw Data..." = "Зберегти необроблені дані...";
|
||||
|
||||
@ -79,6 +117,12 @@
|
||||
/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
|
||||
"Documentation" = "Документація";
|
||||
|
||||
/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
|
||||
"Troubleshooting" = "Усунення несправностей";
|
||||
|
||||
/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
|
||||
"Report A Bug" = "Повідомити про помилку";
|
||||
|
||||
/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
|
||||
"Privacy Policy" = "Політика конфіденційності";
|
||||
|
||||
@ -94,6 +138,18 @@
|
||||
/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
|
||||
"Copy" = "Копіювати";
|
||||
|
||||
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
|
||||
"Cut" = "Вирізати";
|
||||
|
||||
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
|
||||
"Paste" = "Вставити";
|
||||
|
||||
/* myNotebook.py: Label for 'Select All'; */
|
||||
"Select All" = "Вибрати все";
|
||||
|
||||
/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
|
||||
"CAPI auth disabled by killswitch" = "Автентифікація CAPI виключена функцією аварійного відключення";
|
||||
|
||||
/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */
|
||||
"Logging in..." = "Вхід в...";
|
||||
|
||||
@ -109,9 +165,36 @@
|
||||
/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */
|
||||
"Station doesn't have a market!" = "На станції немає ринку!";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */
|
||||
"CAPI query aborted: Cmdr name unknown" = "Запит CAPI скасований: Невідоме ім'я КМДР";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */
|
||||
"CAPI query aborted: Game mode unknown" = "Запит CAPI скасований: Невідомий режим гри";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */
|
||||
"CAPI query aborted: GameVersion unknown" = "Запит CAPI скасований: Невідома версія гри";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */
|
||||
"CAPI query aborted: Current system unknown" = "Запит CAPI скасований: Невідома поточна система";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */
|
||||
"CAPI query aborted: In other-ship multi-crew" = "Запит CAPI скасований: в режимі мультиекіпажу іншого корабля";
|
||||
|
||||
/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */
|
||||
"CAPI query aborted: CQC (Arena) detected" = "Запит CAPI скасований: Арена Близького бою (CQC) виявлена";
|
||||
|
||||
/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */
|
||||
"Fetching data..." = "Отримання даних...";
|
||||
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "Запит CAPI корабля-носія скасований функцією аварійного відключення";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI: Немає даних корабля-носія";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI: Дані корабля-носія неповні";
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI: Не отримано данних пілота";
|
||||
|
||||
@ -124,6 +207,12 @@
|
||||
/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */
|
||||
"What are you flying?!" = "На чому летимо?!";
|
||||
|
||||
/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */
|
||||
"Frontier CAPI server error" = "Frontier CAPI: cерверна помилка";
|
||||
|
||||
/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */
|
||||
"CAPI: Refreshing access token..." = "CAPI: Оновлюємо токен доступу...";
|
||||
|
||||
/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */
|
||||
"Last updated at %H:%M:%S" = "Останнє оновлення було %H:%M:%S";
|
||||
|
||||
@ -154,6 +243,24 @@
|
||||
/* 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: Плагіни без підтримки 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: Поломані плагіни";
|
||||
|
||||
/* 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." = "Не вдалося завантажити один або декілька увімкнених плагінів. Перегляньте список на вкладці '{PLUGINS}' в '{FILE}' > '{SETTINGS}'. Ця помилка могла бути спричинена неправильною структурою папок. Файл load.py повинен знаходитися у папці plugins/PLUGIN_NAME/load.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";
|
||||
|
||||
/* 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" = "Очікуємо повного входу КМДР";
|
||||
|
||||
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
|
||||
"Journal directory already locked" = "Каталог журналу вже заблокований";
|
||||
|
||||
@ -169,9 +276,45 @@
|
||||
/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
|
||||
"Default" = "Стандартне налаштування";
|
||||
|
||||
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
|
||||
"Auto" = "Авто";
|
||||
|
||||
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
|
||||
"Normal" = "Нормальний";
|
||||
|
||||
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
|
||||
"Beta" = "Бета";
|
||||
|
||||
/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
|
||||
"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Вставте URL для використання із корабельними збірками coriolis.io. Зауважте що '/import?data=' ПОВИННО бути в кінці!";
|
||||
|
||||
/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */
|
||||
"Normal URL" = "Нормальний URL";
|
||||
|
||||
/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */
|
||||
"Reset" = "Скинути";
|
||||
|
||||
/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */
|
||||
"Beta URL" = "Бета URL";
|
||||
|
||||
/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:116; */
|
||||
"Override Beta/Normal Selection" = "Перевизначити вибір Бета/Нормальний";
|
||||
|
||||
/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:156; */
|
||||
"Invalid Coriolis override mode!" = "Неправильний режим перевизначення Coriolis!";
|
||||
|
||||
/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */
|
||||
"Error: Can't connect to EDDN" = "Помилка: Немає зв'язку з EDDN!";
|
||||
|
||||
/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:576; */
|
||||
"EDDN Error: EDMC is too old for EDDN. Please update." = "Помилка EDDN: версія EDMC є занадто старою для EDDN. Будь-ласка, оновіть.";
|
||||
|
||||
/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:582; */
|
||||
"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "Помилка EDDN: Перевірка провалена (EDMC занадно старий?). Див. Лог";
|
||||
|
||||
/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:587; */
|
||||
"EDDN Error: Returned {STATUS} status code" = "Помилка EDDN: Код {STATUS} було повернуто.";
|
||||
|
||||
/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:2041; */
|
||||
"Send station data to the Elite Dangerous Data Network" = "Надсилати дані станцій до Elite Dangerous Data Network";
|
||||
|
||||
@ -181,6 +324,9 @@
|
||||
/* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:2063; */
|
||||
"Delay sending until docked" = "Відкласти відправку даних до стикування";
|
||||
|
||||
/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:2178; */
|
||||
"EDDN journal handler disabled. See Log." = "Журнал EDDN відключено. Див. Лог";
|
||||
|
||||
/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:2507; */
|
||||
"Sending data to EDDN..." = "Відправка даних до EDDN...";
|
||||
|
||||
@ -196,6 +342,12 @@
|
||||
/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:394; prefs.py:527; prefs.py:1157; prefs.py:1190; stats.py:154; stats.py:173; stats.py:192; stats.py:209; */
|
||||
"None" = "Нічого";
|
||||
|
||||
/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:516; */
|
||||
"EDSM Handler disabled. See Log." = "Обробник EDSM відключено. Див. Лог";
|
||||
|
||||
/* edsm.py: EDSM - Only Live data; In files: edsm.py:632; */
|
||||
"EDSM only accepts Live galaxy data" = "EDSM приймає дані тільки Live версії галактики";
|
||||
|
||||
/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:916; edsm.py:1048; */
|
||||
"Error: EDSM {MSG}" = "Помилка: EDSM {MSG}!";
|
||||
|
||||
@ -208,12 +360,15 @@
|
||||
/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */
|
||||
"Inara credentials" = "Обліковий запис Inara";
|
||||
|
||||
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; inara.py: Inara - Only Live data; In files: inara.py:384; inara.py:386; */
|
||||
"Inara only accepts Live galaxy data" = "Inara приймає дані тільки Live версії галактики";
|
||||
|
||||
/* inara.py: INARA support disabled via killswitch; In files: inara.py:395; */
|
||||
"Inara disabled. See Log." = "Inara відключено. Див. Лог";
|
||||
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "Помилка: Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "Уподобання";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "Оберіть дані які буде збережено";
|
||||
|
||||
@ -232,9 +387,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "Розташування файлу";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "Внесення змін...";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "Перегляд...";
|
||||
|
||||
@ -244,21 +396,15 @@
|
||||
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
|
||||
"E:D journal file location" = "Розташування файлу-журналу E:D";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "Сполучення клавіш";
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "Налаштування CAPI";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "Ввімкнути запити CAPI кораблів-носіїв";
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "Гаряча клавіша";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "Перезавантажте {APP} для використання ярликів";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} необхідні дозволи для використання ярликів";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "Відкрити налаштування системи";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "Тільки тоді, коли Elite: Dangerous є активним додатком";
|
||||
|
||||
@ -283,6 +429,15 @@
|
||||
/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:681; */
|
||||
"Configuration" = "Конфігурація";
|
||||
|
||||
/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:690; */
|
||||
"Main UI privacy options" = "Налаштування приватності основного вікна.";
|
||||
|
||||
/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:695; */
|
||||
"Hide private group name in UI" = "Приховати назву приватної групи у вікні";
|
||||
|
||||
/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:699; */
|
||||
"Hide multi-crew captain name" = "Приховати ім'я капітана мультиекіпажу";
|
||||
|
||||
/* prefs.py: Preferences privacy tab title; In files: prefs.py:703; */
|
||||
"Privacy" = "Конфіденційність";
|
||||
|
||||
@ -340,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" = "Інформація про міграцію плагінів";
|
||||
|
||||
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
|
||||
"Broken Plugins" = "Поломані плагіни";
|
||||
|
||||
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
|
||||
"Disabled Plugins" = "Вимкнені плагіни";
|
||||
|
||||
@ -352,6 +510,21 @@
|
||||
/* stats.py: Top rank; In files: stats.py:63; */
|
||||
"Elite" = "Еліта";
|
||||
|
||||
/* stats.py: Top rank +1; In files: stats.py:64; */
|
||||
"Elite I" = "Еліта І";
|
||||
|
||||
/* stats.py: Top rank +2; In files: stats.py:65; */
|
||||
"Elite II" = "Еліта ІІ";
|
||||
|
||||
/* stats.py: Top rank +3; In files: stats.py:66; */
|
||||
"Elite III" = "Еліта ІІІ";
|
||||
|
||||
/* stats.py: Top rank +4; In files: stats.py:67; */
|
||||
"Elite IV" = "Еліта IV";
|
||||
|
||||
/* stats.py: Top rank +5; In files: stats.py:68; */
|
||||
"Elite V" = "Еліта V";
|
||||
|
||||
/* stats.py: Ranking; In files: stats.py:74; */
|
||||
"Combat" = "Бойовий";
|
||||
|
||||
@ -361,6 +534,12 @@
|
||||
/* stats.py: Ranking; In files: stats.py:76; */
|
||||
"Explorer" = "Дослідницький";
|
||||
|
||||
/* stats.py: Ranking; In files: stats.py:77; */
|
||||
"Mercenary" = "Найманець";
|
||||
|
||||
/* stats.py: Ranking; In files: stats.py:78; */
|
||||
"Exobiologist" = "Екзобіолог";
|
||||
|
||||
/* stats.py: Ranking; In files: stats.py:79; */
|
||||
"CQC" = "Близький бій (CQC)";
|
||||
|
||||
@ -445,6 +624,51 @@
|
||||
/* stats.py: Explorer rank; In files: stats.py:118; */
|
||||
"Pioneer" = "Піонер";
|
||||
|
||||
/* stats.py: Mercenary rank; In files: stats.py:122; */
|
||||
"Defenceless" = "Беззахисний";
|
||||
|
||||
/* stats.py: Mercenary rank; In files: stats.py:123; */
|
||||
"Mostly Defenceless" = "Здебільшого беззахисний";
|
||||
|
||||
/* stats.py: Mercenary rank; In files: stats.py:124; */
|
||||
"Rookie" = "Новачок";
|
||||
|
||||
/* stats.py: Mercenary rank; In files: stats.py:125; */
|
||||
"Soldier" = "Солдат";
|
||||
|
||||
/* stats.py: Mercenary rank; In files: stats.py:126; stats.py:128; */
|
||||
"Gunslinger" = "Стрілець";
|
||||
|
||||
/* stats.py: Mercenary rank; In files: stats.py:127; */
|
||||
"Warrior" = "Воїн";
|
||||
|
||||
/* stats.py: Mercenary rank; In files: stats.py:129; */
|
||||
"Deadeye" = "Снайпер";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:132; */
|
||||
"Directionless" = "Безнапрямний";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:133; */
|
||||
"Mostly Directionless" = "Здебільшого безнапрямний";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:134; */
|
||||
"Compiler" = "Упорядник";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:135; */
|
||||
"Collector" = "Колектор";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:136; */
|
||||
"Cataloguer" = "Каталогіст";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:137; */
|
||||
"Taxonomist" = "Систематик";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:138; */
|
||||
"Ecologist" = "Еколог";
|
||||
|
||||
/* stats.py: Exobiologist rank; In files: stats.py:139; */
|
||||
"Geneticist" = "Генетик";
|
||||
|
||||
/* stats.py: CQC rank; In files: stats.py:142; */
|
||||
"Helpless" = "Безпорадний";
|
||||
|
||||
@ -565,9 +789,36 @@
|
||||
/* stats.py: Power rank; In files: stats.py:197; */
|
||||
"Rating 5" = "Рейтинг 5";
|
||||
|
||||
/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:315; */
|
||||
"Status: Don't yet know your Commander name" = "Статус: Поки ще не знаємо ім'я вашого КМДР";
|
||||
|
||||
/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:323; */
|
||||
"Status: No CAPI data yet" = "Статус: Поки ще немає даних CAPI";
|
||||
|
||||
/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:409; */
|
||||
"Value" = "Вартість";
|
||||
|
||||
/* 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} доступна";
|
||||
|
||||
/* prefs.py: Stable Version of EDMC; */
|
||||
"Stable" = "Стабільний";
|
||||
|
||||
/* prefs.py: Select the Update Track (Beta, Stable); */
|
||||
"Update Track" = "Шлях оновлень";
|
||||
|
||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||
"Update Track Changed to {TRACK}" = "Шлях оновлень змінено на {TRACK}";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Шлях оновлень змінено з \"Бета\" на \"Стабільний\". Ви більше не будете отримувати бета оновлень. Ви залишитеся на версії бета до наступного стабільного релізу.\n\nВи можете вручну вернутися до останньої стабільної версії. Щоб це зробити, вам необхідно завантажити останню стабільну версію вручну. Зауважте що заниження версії між релізами з багатьма змінами може спричинити помилки чи повністю унеможливити роботу програми.\n\nБажаєте відкрити сторінку GitHub для завантаження останнього релізу?";
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Неможливо вставити нетекстовий вміст буфера обміну.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Відкрити у {URL}";
|
||||
|
@ -70,12 +70,6 @@
|
||||
/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
|
||||
"Edit" = "编辑";
|
||||
|
||||
/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
|
||||
"View" = "显示";
|
||||
|
||||
/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
|
||||
"Window" = "窗口";
|
||||
|
||||
/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
|
||||
"Help" = "帮助";
|
||||
|
||||
@ -157,12 +151,12 @@
|
||||
/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
|
||||
"CAPI fleetcarrier disabled by killswitch" = "CAPI 舰队母舰 (fleet carrier) 被 killswitch 禁用";
|
||||
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
|
||||
"CAPI: No fleetcarrier data returned" = "CAPI:无舰队母舰 (fleet carrier) 数据";
|
||||
|
||||
/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
|
||||
"CAPI: Fleetcarrier data incomplete" = "CAPI:舰队母舰 (fleet carrier) 数据不完整";
|
||||
|
||||
/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
|
||||
"CAPI: No commander data returned" = "CAPI:没有指挥官数据";
|
||||
|
||||
@ -319,9 +313,6 @@
|
||||
/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
|
||||
"Error: Inara {MSG}" = "错误:Inara {MSG}";
|
||||
|
||||
/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
|
||||
"Preferences" = "偏好";
|
||||
|
||||
/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
|
||||
"Please choose what data to save" = "请选择想要保存的数据";
|
||||
|
||||
@ -340,9 +331,6 @@
|
||||
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
|
||||
"File location" = "保存位置";
|
||||
|
||||
/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
|
||||
"Change..." = "更改…";
|
||||
|
||||
/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
|
||||
"Browse..." = "浏览…";
|
||||
|
||||
@ -355,24 +343,12 @@
|
||||
/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
|
||||
"CAPI Settings" = "CAPI 设置";
|
||||
|
||||
|
||||
/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
|
||||
"Enable Fleetcarrier CAPI Queries" = "开启舰队母舰 (fleet carrier) CAPI 访问";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
|
||||
"Keyboard shortcut" = "键盘快捷键";
|
||||
|
||||
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
|
||||
"Hotkey" = "快捷键";
|
||||
|
||||
/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
|
||||
"Re-start {APP} to use shortcuts" = "重启 {APP} 以使用快捷键";
|
||||
|
||||
/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
|
||||
"{APP} needs permission to use shortcuts" = "{APP} 需要权限以使用快捷键";
|
||||
|
||||
/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
|
||||
"Open System Preferences" = "打开系统偏好设置";
|
||||
|
||||
/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
|
||||
"Only when Elite: Dangerous is the active app" = "仅当 Elite: Dangerous 在前台运行时生效";
|
||||
|
||||
|
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.
|
||||
|
16
build.py
@ -21,6 +21,7 @@ from config import (
|
||||
_static_appversion,
|
||||
update_interval
|
||||
)
|
||||
from update import check_for_fdev_updates
|
||||
|
||||
|
||||
def iss_build(template_path: str, output_file: str) -> None:
|
||||
@ -76,10 +77,8 @@ def generate_data_files(
|
||||
"ChangeLog.md",
|
||||
"snd_good.wav",
|
||||
"snd_bad.wav",
|
||||
"modules.p", # TODO: Remove in 6.0
|
||||
"modules.json",
|
||||
"ships.json",
|
||||
"ships.p", # TODO: Remove in 6.0
|
||||
f"{app_name}.ico",
|
||||
f"resources/{appcmdname}.ico",
|
||||
"EDMarketConnector - TRACE.bat",
|
||||
@ -133,7 +132,6 @@ def build() -> None:
|
||||
"distutils",
|
||||
"_markerlib",
|
||||
"optparse",
|
||||
"PIL",
|
||||
"simplejson",
|
||||
"unittest",
|
||||
"doctest",
|
||||
@ -178,10 +176,19 @@ def build() -> None:
|
||||
],
|
||||
}
|
||||
|
||||
checker_config: dict = {
|
||||
"dest_base": "EDMCSystemProfiler",
|
||||
"script": "EDMCSystemProfiler.py",
|
||||
"icon_resources": [(0, f"{appname}.ico")],
|
||||
"other_resources": [
|
||||
(24, 1, pathlib.Path(f"resources/{appname}.manifest").read_text(encoding="UTF8"))
|
||||
],
|
||||
}
|
||||
|
||||
try:
|
||||
py2exe.freeze(
|
||||
version_info=version_info,
|
||||
windows=[windows_config],
|
||||
windows=[windows_config, checker_config],
|
||||
console=[console_config],
|
||||
data_files=data_files,
|
||||
options=options,
|
||||
@ -201,4 +208,5 @@ def build() -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_for_fdev_updates()
|
||||
build()
|
||||
|
39
companion.py
@ -28,7 +28,7 @@ import urllib.parse
|
||||
import webbrowser
|
||||
from email.utils import parsedate
|
||||
from queue import Queue
|
||||
from typing import TYPE_CHECKING, Any, Mapping, OrderedDict, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, Mapping, TypeVar
|
||||
import requests
|
||||
import config as conf_module
|
||||
import killswitch
|
||||
@ -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): 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')
|
||||
@ -1064,7 +1063,7 @@ class Session:
|
||||
play_sound: bool = False, auto_update: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Perform CAPI query for fleetcarrier data.
|
||||
Perform CAPI query for Fleet Carrier data.
|
||||
|
||||
:param query_time: When this query was initiated.
|
||||
:param tk_response_event: Name of tk event to generate when response queued.
|
||||
@ -1075,8 +1074,8 @@ class Session:
|
||||
if not capi_host:
|
||||
return
|
||||
|
||||
# Ask the thread worker to perform a fleetcarrier query
|
||||
logger.trace_if('capi.worker', 'Enqueueing fleetcarrier request')
|
||||
# Ask the thread worker to perform a Fleet Carrier query
|
||||
logger.trace_if('capi.worker', 'Enqueueing Fleet Carrier request')
|
||||
self.capi_request_queue.put(
|
||||
EDMCCAPIRequest(
|
||||
capi_host=capi_host,
|
||||
@ -1328,7 +1327,7 @@ def index_possibly_sparse_list(data: Mapping[str, V] | list[V], key: int) -> V:
|
||||
if isinstance(data, list):
|
||||
return data[key]
|
||||
|
||||
if isinstance(data, (dict, OrderedDict)):
|
||||
if isinstance(data, (dict, dict)):
|
||||
return data[str(key)]
|
||||
|
||||
raise ValueError(f'Unexpected data type {type(data)}')
|
||||
|
@ -7,7 +7,6 @@ See LICENSE file.
|
||||
|
||||
Windows uses the Registry to store values in a flat manner.
|
||||
Linux uses a file, but for commonality it's still a flat data structure.
|
||||
macOS uses a 'defaults' object.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@ -18,7 +17,6 @@ __all__ = [
|
||||
'applongname',
|
||||
'appcmdname',
|
||||
'copyright',
|
||||
'update_feed',
|
||||
'update_interval',
|
||||
'debug_senders',
|
||||
'trace_on',
|
||||
@ -30,7 +28,9 @@ __all__ = [
|
||||
'user_agent',
|
||||
'appversion_nobuild',
|
||||
'AbstractConfig',
|
||||
'config'
|
||||
'config',
|
||||
'get_update_feed',
|
||||
'update_feed'
|
||||
]
|
||||
|
||||
import abc
|
||||
@ -54,11 +54,11 @@ 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.6'
|
||||
_static_appversion = '5.11.0'
|
||||
_cached_version: semantic_version.Version | None = None
|
||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
||||
|
||||
update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml'
|
||||
|
||||
update_interval = 8*60*60 # 8 Hours
|
||||
# Providers marked to be in debug mode. Generally this is expected to switch to sending data to a log file
|
||||
debug_senders: list[str] = []
|
||||
@ -467,10 +467,6 @@ def get_config(*args, **kwargs) -> AbstractConfig:
|
||||
:param kwargs: Args to be passed through to implementation.
|
||||
:return: Instance of the implementation.
|
||||
"""
|
||||
if sys.platform == "darwin": # pragma: sys-platform-darwin
|
||||
from .darwin import MacConfig
|
||||
return MacConfig(*args, **kwargs)
|
||||
|
||||
if sys.platform == "win32": # pragma: sys-platform-win32
|
||||
from .windows import WinConfig
|
||||
return WinConfig(*args, **kwargs)
|
||||
@ -483,3 +479,15 @@ def get_config(*args, **kwargs) -> AbstractConfig:
|
||||
|
||||
|
||||
config = get_config()
|
||||
|
||||
|
||||
# Wiki: https://github.com/EDCD/EDMarketConnector/wiki/Participating-in-Open-Betas-of-EDMC
|
||||
def get_update_feed() -> str:
|
||||
"""Select the proper update feed for the current update track."""
|
||||
if config.get_bool('beta_optin'):
|
||||
return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector-beta.xml'
|
||||
return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml'
|
||||
|
||||
|
||||
# WARNING: update_feed is deprecated, and will be removed in 6.0 or later. Please migrate to get_update_feed()
|
||||
update_feed = get_update_feed()
|
||||
|
191
config/darwin.py
@ -1,191 +0,0 @@
|
||||
"""
|
||||
darwin.py - Darwin/macOS implementation of AbstractConfig.
|
||||
|
||||
Copyright (c) EDCD, All Rights Reserved
|
||||
Licensed under the GNU General Public License.
|
||||
See LICENSE file.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Any
|
||||
from Foundation import ( # type: ignore
|
||||
NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults,
|
||||
NSUserDomainMask
|
||||
)
|
||||
from config import AbstractConfig, appname, logger
|
||||
|
||||
assert sys.platform == 'darwin'
|
||||
|
||||
|
||||
class MacConfig(AbstractConfig):
|
||||
"""MacConfig is the implementation of AbstractConfig for Darwin based OSes."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
support_path = pathlib.Path(
|
||||
NSSearchPathForDirectoriesInDomains(
|
||||
NSApplicationSupportDirectory, NSUserDomainMask, True
|
||||
)[0]
|
||||
)
|
||||
|
||||
self.app_dir_path = support_path / appname
|
||||
self.app_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||
|
||||
# Bundle IDs identify a singled app though out a system
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
exe_dir = pathlib.Path(sys.executable).parent
|
||||
self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins'
|
||||
self.respath_path = exe_dir.parent / 'Resources'
|
||||
self.identifier = NSBundle.mainBundle().bundleIdentifier()
|
||||
|
||||
else:
|
||||
file_dir = pathlib.Path(__file__).parent.parent
|
||||
self.internal_plugin_dir_path = file_dir / 'plugins'
|
||||
self.respath_path = file_dir
|
||||
|
||||
self.identifier = f'uk.org.marginal.{appname.lower()}'
|
||||
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
|
||||
|
||||
self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous'
|
||||
self._defaults: Any = NSUserDefaults.standardUserDefaults()
|
||||
self._settings: dict[str, int | str | list] = dict(
|
||||
self._defaults.persistentDomainForName_(self.identifier) or {}
|
||||
) # make writeable
|
||||
|
||||
if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists():
|
||||
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
|
||||
|
||||
def __raw_get(self, key: str) -> None | list | str | int:
|
||||
"""
|
||||
Retrieve the raw data for the given key.
|
||||
|
||||
:param str: str - The key data is being requested for.
|
||||
:return: The requested data.
|
||||
"""
|
||||
res = self._settings.get(key)
|
||||
# On MacOS Catalina, with python.org python 3.9.2 any 'list'
|
||||
# has type __NSCFArray so a simple `isinstance(res, list)` is
|
||||
# False. So, check it's not-None, and not the other types.
|
||||
#
|
||||
# If we can find where to import the definition of NSCFArray
|
||||
# then we could possibly test against that.
|
||||
if res is not None and not isinstance(res, str) and not isinstance(res, int):
|
||||
return list(res)
|
||||
|
||||
return res
|
||||
|
||||
def get_str(self, key: str, *, default: str = None) -> str:
|
||||
"""
|
||||
Return the string referred to by the given key if it exists, or the default.
|
||||
|
||||
Implements :meth:`AbstractConfig.get_str`.
|
||||
"""
|
||||
res = self.__raw_get(key)
|
||||
if res is None:
|
||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
||||
|
||||
if not isinstance(res, str):
|
||||
raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}')
|
||||
|
||||
return res
|
||||
|
||||
def get_list(self, key: str, *, default: list = None) -> list:
|
||||
"""
|
||||
Return the list referred to by the given key if it exists, or the default.
|
||||
|
||||
Implements :meth:`AbstractConfig.get_list`.
|
||||
"""
|
||||
res = self.__raw_get(key)
|
||||
if res is None:
|
||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
||||
|
||||
if not isinstance(res, list):
|
||||
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
||||
|
||||
return res
|
||||
|
||||
def get_int(self, key: str, *, default: int = 0) -> int:
|
||||
"""
|
||||
Return the int referred to by key if it exists in the config.
|
||||
|
||||
Implements :meth:`AbstractConfig.get_int`.
|
||||
"""
|
||||
res = self.__raw_get(key)
|
||||
if res is None:
|
||||
return default
|
||||
|
||||
if not isinstance(res, (str, int)):
|
||||
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
||||
|
||||
try:
|
||||
return int(res)
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}')
|
||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
||||
|
||||
def get_bool(self, key: str, *, default: bool = None) -> bool:
|
||||
"""
|
||||
Return the bool referred to by the given key if it exists, or the default.
|
||||
|
||||
Implements :meth:`AbstractConfig.get_bool`.
|
||||
"""
|
||||
res = self.__raw_get(key)
|
||||
if res is None:
|
||||
return default # Yes it could be None, but we're _assuming_ that people gave us a default
|
||||
|
||||
if not isinstance(res, bool):
|
||||
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
|
||||
|
||||
return res
|
||||
|
||||
def set(self, key: str, val: int | str | list[str] | bool) -> None:
|
||||
"""
|
||||
Set the given key's data to the given value.
|
||||
|
||||
Implements :meth:`AbstractConfig.set`.
|
||||
"""
|
||||
if self._settings is None:
|
||||
raise ValueError('attempt to use a closed _settings')
|
||||
|
||||
if not isinstance(val, (bool, str, int, list)):
|
||||
raise ValueError(f'Unexpected type for value {type(val)=}')
|
||||
|
||||
self._settings[key] = val
|
||||
|
||||
def delete(self, key: str, *, suppress=False) -> None:
|
||||
"""
|
||||
Delete the given key from the config.
|
||||
|
||||
Implements :meth:`AbstractConfig.delete`.
|
||||
"""
|
||||
try:
|
||||
del self._settings[key]
|
||||
|
||||
except Exception:
|
||||
if suppress:
|
||||
pass
|
||||
|
||||
def save(self) -> None:
|
||||
"""
|
||||
Save the current configuration.
|
||||
|
||||
Implements :meth:`AbstractConfig.save`.
|
||||
"""
|
||||
self._defaults.setPersistentDomain_forName_(self._settings, self.identifier)
|
||||
self._defaults.synchronize()
|
||||
|
||||
def close(self) -> None:
|
||||
"""
|
||||
Close this config and release any associated resources.
|
||||
|
||||
Implements :meth:`AbstractConfig.close`.
|
||||
"""
|
||||
self.save()
|
||||
self._defaults = None
|
@ -1 +1 @@
|
||||
Subproject commit 8adfd86b64e8c14e873d2f5123d88ca6743420b9
|
||||
Subproject commit 651aab5af6a22980a1f88dcbb9ed256244cd6dff
|
@ -16,7 +16,6 @@ FDevIDs/ version of the file, copy it over the local one.
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
import outfitting
|
||||
from edmc_data import coriolis_ship_map, ship_name_map
|
||||
@ -56,7 +55,7 @@ if __name__ == "__main__":
|
||||
for i, bulkhead in enumerate(bulkheads):
|
||||
modules['_'.join([reverse_ship_map[name], 'armour', bulkhead])] = {'mass': m['bulkheads'][i]['mass']}
|
||||
|
||||
ships = OrderedDict([(k, ships[k]) for k in sorted(ships)]) # sort for easier diffing
|
||||
ships = {k: ships[k] for k in sorted(ships)}
|
||||
with open("ships.json", "w") as ships_file:
|
||||
json.dump(ships, ships_file, indent=4)
|
||||
|
||||
@ -91,6 +90,6 @@ if __name__ == "__main__":
|
||||
add(modules, 'hpt_multicannon_fixed_small_advanced', {'mass': 2})
|
||||
add(modules, 'hpt_multicannon_fixed_medium_advanced', {'mass': 4})
|
||||
|
||||
modules = OrderedDict([(k, modules[k]) for k in sorted(modules)]) # sort for easier diffing
|
||||
modules = {k: modules[k] for k in sorted(modules)}
|
||||
with open("modules.json", "w") as modules_file:
|
||||
json.dump(modules, modules_file, indent=4)
|
||||
|
@ -20,13 +20,13 @@ from EDMCLogging import get_main_logger
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
if sys.platform in ('darwin', 'win32'):
|
||||
if sys.platform == 'win32':
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
else:
|
||||
# Linux's inotify doesn't work over CIFS or NFS, so poll
|
||||
class FileSystemEventHandler: # type: ignore
|
||||
"""Dummy class to represent a file system event handler on platforms other than macOS and Windows."""
|
||||
"""Dummy class to represent a file system event handler on platforms other than Windows."""
|
||||
|
||||
|
||||
class Dashboard(FileSystemEventHandler):
|
||||
@ -160,7 +160,7 @@ class Dashboard(FileSystemEventHandler):
|
||||
|
||||
def on_modified(self, event) -> None:
|
||||
"""
|
||||
Watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows.
|
||||
Watchdog callback - FileModifiedEvent on Windows.
|
||||
|
||||
:param event: Watchdog event.
|
||||
"""
|
||||
|
@ -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.";
|
||||
|
||||
|
@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import tkinter as tk
|
||||
|
||||
import myNotebook as nb # noqa: N813
|
||||
from config import appname, config
|
||||
|
||||
@ -47,7 +48,7 @@ class ClickCounter:
|
||||
"""
|
||||
self.on_preferences_closed("", False) # Save our prefs
|
||||
|
||||
def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None:
|
||||
def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None:
|
||||
"""
|
||||
setup_preferences is called by plugin_prefs below.
|
||||
|
||||
@ -63,7 +64,7 @@ class ClickCounter:
|
||||
|
||||
# setup our config in a "Click Count: number"
|
||||
nb.Label(frame, text='Click Count').grid(row=current_row)
|
||||
nb.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
|
||||
nb.EntryMenu(frame, textvariable=self.click_count).grid(row=current_row, column=1)
|
||||
current_row += 1 # Always increment our row counter, makes for far easier tkinter design.
|
||||
return frame
|
||||
|
||||
@ -126,7 +127,7 @@ def plugin_stop() -> None:
|
||||
return cc.on_unload()
|
||||
|
||||
|
||||
def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None:
|
||||
def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None:
|
||||
"""
|
||||
Handle preferences tab for the plugin.
|
||||
|
||||
|
16
edmc_data.py
@ -4,7 +4,6 @@ Static data.
|
||||
For easy reference any variable should be prefixed with the name of the file it
|
||||
was either in originally, or where the primary code utilising it is.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
|
||||
# Map numeric 'demand/supply brackets' to the names as shown in-game.
|
||||
commodity_bracketmap = {
|
||||
@ -57,13 +56,14 @@ edshipyard_slot_map = {
|
||||
|
||||
# Map API module names to in-game names
|
||||
|
||||
outfitting_armour_map = OrderedDict([
|
||||
('grade1', 'Lightweight Alloy'),
|
||||
('grade2', 'Reinforced Alloy'),
|
||||
('grade3', 'Military Grade Composite'),
|
||||
('mirrored', 'Mirrored Surface Composite'),
|
||||
('reactive', 'Reactive Surface Composite'),
|
||||
])
|
||||
outfitting_armour_map = {
|
||||
'grade1': 'Lightweight Alloy',
|
||||
'grade2': 'Reinforced Alloy',
|
||||
'grade3': 'Military Grade Composite',
|
||||
'mirrored': 'Mirrored Surface Composite',
|
||||
'reactive': 'Reactive Surface Composite',
|
||||
}
|
||||
|
||||
|
||||
outfitting_weapon_map = {
|
||||
'advancedtorppylon': 'Torpedo Pylon',
|
||||
|
@ -76,10 +76,6 @@ def get_hotkeymgr() -> AbstractHotkeyMgr:
|
||||
:return: Appropriate class instance.
|
||||
:raises ValueError: If unsupported platform.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
from hotkey.darwin import MacHotkeyMgr
|
||||
return MacHotkeyMgr()
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from hotkey.windows import WindowsHotkeyMgr
|
||||
return WindowsHotkeyMgr()
|
||||
|
276
hotkey/darwin.py
@ -1,276 +0,0 @@
|
||||
"""darwin/macOS implementation of hotkey.AbstractHotkeyMgr."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from typing import Callable
|
||||
assert sys.platform == 'darwin'
|
||||
|
||||
import objc
|
||||
from AppKit import (
|
||||
NSAlternateKeyMask, NSApplication, NSBeep, NSClearLineFunctionKey, NSCommandKeyMask, NSControlKeyMask,
|
||||
NSDeleteFunctionKey, NSDeviceIndependentModifierFlagsMask, NSEvent, NSF1FunctionKey, NSF35FunctionKey,
|
||||
NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace
|
||||
)
|
||||
|
||||
from config import config
|
||||
from EDMCLogging import get_main_logger
|
||||
from hotkey import AbstractHotkeyMgr
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
|
||||
class MacHotkeyMgr(AbstractHotkeyMgr):
|
||||
"""Hot key management."""
|
||||
|
||||
POLL = 250
|
||||
# https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/#//apple_ref/doc/constant_group/Function_Key_Unicodes
|
||||
DISPLAY = {
|
||||
0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫',
|
||||
0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→',
|
||||
0xf727: u'Ins',
|
||||
0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘',
|
||||
0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock',
|
||||
0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset',
|
||||
0xf739: u'⌧',
|
||||
}
|
||||
(ACQUIRE_INACTIVE, ACQUIRE_ACTIVE, ACQUIRE_NEW) = range(3)
|
||||
|
||||
def __init__(self):
|
||||
self.MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask \
|
||||
| NSNumericPadKeyMask
|
||||
self.root: tk.Tk
|
||||
|
||||
self.keycode = 0
|
||||
self.modifiers = 0
|
||||
self.activated = False
|
||||
self.observer = None
|
||||
|
||||
self.acquire_key = 0
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
|
||||
self.tkProcessKeyEvent_old: Callable
|
||||
|
||||
self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_(
|
||||
pathlib.Path(config.respath_path) / 'snd_good.wav', False
|
||||
)
|
||||
self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_(
|
||||
pathlib.Path(config.respath_path) / 'snd_bad.wav', False
|
||||
)
|
||||
|
||||
def register(self, root: tk.Tk, keycode: int, modifiers: int) -> None:
|
||||
"""
|
||||
Register current hotkey for monitoring.
|
||||
|
||||
:param root: parent window.
|
||||
:param keycode: Key to monitor.
|
||||
:param modifiers: Any modifiers to take into account.
|
||||
"""
|
||||
self.root = root
|
||||
self.keycode = keycode
|
||||
self.modifiers = modifiers
|
||||
self.activated = False
|
||||
|
||||
if keycode:
|
||||
if not self.observer:
|
||||
self.root.after_idle(self._observe)
|
||||
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
||||
|
||||
# Monkey-patch tk (tkMacOSXKeyEvent.c)
|
||||
if not callable(self.tkProcessKeyEvent_old):
|
||||
sel = b'tkProcessKeyEvent:'
|
||||
cls = NSApplication.sharedApplication().class__() # type: ignore
|
||||
self.tkProcessKeyEvent_old = NSApplication.sharedApplication().methodForSelector_(sel) # type: ignore
|
||||
newmethod = objc.selector( # type: ignore
|
||||
self.tkProcessKeyEvent,
|
||||
selector=self.tkProcessKeyEvent_old.selector,
|
||||
signature=self.tkProcessKeyEvent_old.signature
|
||||
)
|
||||
objc.classAddMethod(cls, sel, newmethod) # type: ignore
|
||||
|
||||
def tkProcessKeyEvent(self, cls, the_event): # noqa: N802
|
||||
"""
|
||||
Monkey-patch tk (tkMacOSXKeyEvent.c).
|
||||
|
||||
- workaround crash on OSX 10.9 & 10.10 on seeing a composing character
|
||||
- notice when modifier key state changes
|
||||
- keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey
|
||||
|
||||
(Would like to use a decorator but need to ensure the application is created before this is installed)
|
||||
:param cls: ???
|
||||
:param the_event: tk event
|
||||
:return: ???
|
||||
"""
|
||||
if self.acquire_state:
|
||||
if the_event.type() == NSFlagsChanged:
|
||||
self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
||||
# suppress the event by not chaining the old function
|
||||
return the_event
|
||||
|
||||
if the_event.type() in (NSKeyDown, NSKeyUp):
|
||||
c = the_event.charactersIgnoringModifiers()
|
||||
self.acquire_key = (c and ord(c[0]) or 0) | \
|
||||
(the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask)
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
|
||||
# suppress the event by not chaining the old function
|
||||
return the_event
|
||||
|
||||
# replace empty characters with charactersIgnoringModifiers to avoid crash
|
||||
elif the_event.type() in (NSKeyDown, NSKeyUp) and not the_event.characters():
|
||||
the_event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_( # noqa: E501
|
||||
# noqa: E501
|
||||
the_event.type(),
|
||||
the_event.locationInWindow(),
|
||||
the_event.modifierFlags(),
|
||||
the_event.timestamp(),
|
||||
the_event.windowNumber(),
|
||||
the_event.context(),
|
||||
the_event.charactersIgnoringModifiers(),
|
||||
the_event.charactersIgnoringModifiers(),
|
||||
the_event.isARepeat(),
|
||||
the_event.keyCode()
|
||||
)
|
||||
return self.tkProcessKeyEvent_old(cls, the_event)
|
||||
|
||||
def _observe(self):
|
||||
# Must be called after root.mainloop() so that the app's message loop has been created
|
||||
self.observer = NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, self._handler)
|
||||
|
||||
def _poll(self):
|
||||
if config.shutting_down:
|
||||
return
|
||||
|
||||
# No way of signalling to Tkinter from within the callback handler block that doesn't
|
||||
# cause Python to crash, so poll.
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
self.root.event_generate('<<Invoke>>', when="tail")
|
||||
|
||||
if self.keycode or self.modifiers:
|
||||
self.root.after(MacHotkeyMgr.POLL, self._poll)
|
||||
|
||||
def unregister(self) -> None:
|
||||
"""Remove hotkey registration."""
|
||||
self.keycode = 0
|
||||
self.modifiers = 0
|
||||
|
||||
@objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_)
|
||||
def _handler(self, event) -> None:
|
||||
# use event.charactersIgnoringModifiers to handle composing characters like Alt-e
|
||||
if (
|
||||
(event.modifierFlags() & self.MODIFIERMASK) == self.modifiers
|
||||
and ord(event.charactersIgnoringModifiers()[0]) == self.keycode
|
||||
):
|
||||
if config.get_int('hotkey_always'):
|
||||
self.activated = True
|
||||
|
||||
else: # Only trigger if game client is front process
|
||||
front = NSWorkspace.sharedWorkspace().frontmostApplication()
|
||||
if front and front.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
|
||||
self.activated = True
|
||||
|
||||
def acquire_start(self) -> None:
|
||||
"""Start acquiring hotkey state via polling."""
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
||||
self.root.after_idle(self._acquire_poll)
|
||||
|
||||
def acquire_stop(self) -> None:
|
||||
"""Stop acquiring hotkey state."""
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
|
||||
def _acquire_poll(self) -> None:
|
||||
"""Perform a poll of current hotkey state."""
|
||||
if config.shutting_down:
|
||||
return
|
||||
|
||||
# No way of signalling to Tkinter from within the monkey-patched event handler that doesn't
|
||||
# cause Python to crash, so poll.
|
||||
if self.acquire_state:
|
||||
if self.acquire_state == MacHotkeyMgr.ACQUIRE_NEW:
|
||||
# Abuse tkEvent's keycode field to hold our acquired key & modifier
|
||||
self.root.event_generate('<KeyPress>', keycode=self.acquire_key)
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
|
||||
self.root.after(50, self._acquire_poll)
|
||||
|
||||
def fromevent(self, event) -> bool | tuple | None:
|
||||
"""
|
||||
Return configuration (keycode, modifiers) or None=clear or False=retain previous.
|
||||
|
||||
:param event: tk event ?
|
||||
:return: False to retain previous, None to not use, else (keycode, modifiers)
|
||||
"""
|
||||
(keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000) # Set by _acquire_poll()
|
||||
if (
|
||||
keycode
|
||||
and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))
|
||||
):
|
||||
if keycode == 0x1b: # Esc = retain previous
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
return False
|
||||
|
||||
# BkSp, Del, Clear = clear hotkey
|
||||
if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)):
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
return None
|
||||
|
||||
# don't allow keys needed for typing in System Map
|
||||
if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a:
|
||||
NSBeep()
|
||||
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
|
||||
return None
|
||||
|
||||
return keycode, modifiers
|
||||
|
||||
def display(self, keycode, modifiers) -> str:
|
||||
"""
|
||||
Return displayable form of given hotkey + modifiers.
|
||||
|
||||
:param keycode:
|
||||
:param modifiers:
|
||||
:return: string form
|
||||
"""
|
||||
text = ''
|
||||
if modifiers & NSControlKeyMask:
|
||||
text += u'⌃'
|
||||
|
||||
if modifiers & NSAlternateKeyMask:
|
||||
text += u'⌥'
|
||||
|
||||
if modifiers & NSShiftKeyMask:
|
||||
text += u'⇧'
|
||||
|
||||
if modifiers & NSCommandKeyMask:
|
||||
text += u'⌘'
|
||||
|
||||
if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f:
|
||||
text += u'№'
|
||||
|
||||
if not keycode:
|
||||
pass
|
||||
|
||||
elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey):
|
||||
text += f'F{keycode + 1 - ord(NSF1FunctionKey)}'
|
||||
|
||||
elif keycode in MacHotkeyMgr.DISPLAY: # specials
|
||||
text += MacHotkeyMgr.DISPLAY[keycode]
|
||||
|
||||
elif keycode < 0x20: # control keys
|
||||
text += chr(keycode + 0x40)
|
||||
|
||||
elif keycode < 0xf700: # key char
|
||||
text += chr(keycode).upper()
|
||||
|
||||
else:
|
||||
text += u'⁈'
|
||||
|
||||
return text
|
||||
|
||||
def play_good(self):
|
||||
"""Play the 'good' sound."""
|
||||
self.snd_good.play()
|
||||
|
||||
def play_bad(self):
|
||||
"""Play the 'bad' sound."""
|
||||
self.snd_bad.play()
|
BIN
img/Balance.png
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
img/win.png
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
img/win_dark.png
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 54 KiB |
@ -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,16 +208,12 @@ 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':
|
||||
self.attributes('-toolwindow', tk.TRUE)
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
# http://wiki.tcl.tk/13428
|
||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
||||
|
||||
self.resizable(tk.FALSE, tk.FALSE)
|
||||
|
||||
frame = ttk.Frame(self)
|
||||
@ -229,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)
|
||||
|
||||
|
@ -361,8 +361,8 @@ def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> KillSwitchJSONFile | N
|
||||
logger.warning(f"Failed to get kill switches, data was invalid: {e}")
|
||||
return None
|
||||
|
||||
except (requests.exceptions.BaseHTTPError, requests.exceptions.ConnectionError) as e: # type: ignore
|
||||
logger.warning(f"unable to connect to {target!r}: {e}")
|
||||
except requests.exceptions.RequestException as requ_err:
|
||||
logger.warning(f"unable to connect to {target!r}: {requ_err}")
|
||||
return None
|
||||
|
||||
return data
|
||||
|
138
l10n.py
@ -16,17 +16,14 @@ import numbers
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
from contextlib import suppress
|
||||
from os import pardir, listdir, sep, makedirs
|
||||
from os.path import basename, dirname, isdir, isfile, join, abspath, exists
|
||||
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, '')
|
||||
@ -40,12 +37,7 @@ logger = get_main_logger()
|
||||
LANGUAGE_ID = '!Language'
|
||||
LOCALISATION_DIR = 'L10n'
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
from Foundation import ( # type: ignore # exists on Darwin
|
||||
NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle
|
||||
)
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR
|
||||
if TYPE_CHECKING:
|
||||
@ -67,7 +59,16 @@ elif 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'
|
||||
@ -85,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
|
||||
@ -94,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():
|
||||
@ -128,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]:
|
||||
@ -141,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()
|
||||
|
||||
@ -155,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}')
|
||||
@ -179,26 +208,20 @@ class _Translations:
|
||||
def available(self) -> set[str]:
|
||||
"""Return a list of available language codes."""
|
||||
path = self.respath()
|
||||
if getattr(sys, 'frozen', False) and sys.platform == 'darwin':
|
||||
available = {
|
||||
x[:-len('.lproj')] for x in listdir(path)
|
||||
if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))
|
||||
}
|
||||
|
||||
else:
|
||||
available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
|
||||
available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
|
||||
|
||||
return available
|
||||
|
||||
def available_names(self) -> dict[str | None, str]:
|
||||
"""Available language names by code."""
|
||||
names: dict[str | None, str] = OrderedDict([
|
||||
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
|
||||
|
||||
@ -207,9 +230,6 @@ class _Translations:
|
||||
def respath(self) -> str:
|
||||
"""Path to localisation files."""
|
||||
if getattr(sys, 'frozen', False):
|
||||
if sys.platform == 'darwin':
|
||||
return abspath(join(dirname(sys.executable), pardir, 'Resources'))
|
||||
|
||||
return abspath(join(dirname(sys.executable), LOCALISATION_DIR))
|
||||
|
||||
if __file__:
|
||||
@ -235,10 +255,6 @@ class _Translations:
|
||||
except OSError:
|
||||
logger.exception(f'could not open {file_path}')
|
||||
|
||||
elif getattr(sys, 'frozen', False) and sys.platform == 'darwin':
|
||||
res_path = join(self.respath(), f'{lang}.lproj', 'Localizable.strings')
|
||||
return open(res_path, encoding='utf-16')
|
||||
|
||||
res_path = join(self.respath(), f'{lang}.strings')
|
||||
return open(res_path, encoding='utf-8')
|
||||
|
||||
@ -246,15 +262,6 @@ class _Translations:
|
||||
class _Locale:
|
||||
"""Locale holds a few utility methods to convert data to and from localized versions."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
if sys.platform == 'darwin':
|
||||
self.int_formatter = NSNumberFormatter.alloc().init()
|
||||
self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
|
||||
self.float_formatter = NSNumberFormatter.alloc().init()
|
||||
self.float_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
|
||||
self.float_formatter.setMinimumFractionDigits_(5)
|
||||
self.float_formatter.setMaximumFractionDigits_(5)
|
||||
|
||||
def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802
|
||||
warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
|
||||
return self.string_from_number(number, decimals) # type: ignore
|
||||
@ -280,14 +287,6 @@ class _Locale:
|
||||
if decimals == 0 and not isinstance(number, numbers.Integral):
|
||||
number = int(round(number))
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
if not decimals and isinstance(number, numbers.Integral):
|
||||
return self.int_formatter.stringFromNumber_(number)
|
||||
|
||||
self.float_formatter.setMinimumFractionDigits_(decimals)
|
||||
self.float_formatter.setMaximumFractionDigits_(decimals)
|
||||
return self.float_formatter.stringFromNumber_(number)
|
||||
|
||||
if not decimals and isinstance(number, numbers.Integral):
|
||||
return locale.format_string('%d', number, True)
|
||||
return locale.format_string('%.*f', (decimals, number), True)
|
||||
@ -300,9 +299,6 @@ class _Locale:
|
||||
:param string: The string to convert
|
||||
:return: None if the string cannot be parsed, otherwise an int or float dependant on input data.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
return self.float_formatter.numberFromString_(string)
|
||||
|
||||
with suppress(ValueError):
|
||||
return locale.atoi(string)
|
||||
|
||||
@ -333,10 +329,8 @@ class _Locale:
|
||||
:return: The preferred language list
|
||||
"""
|
||||
languages: Iterable[str]
|
||||
if sys.platform == 'darwin':
|
||||
languages = NSLocale.preferredLanguages()
|
||||
|
||||
elif sys.platform != 'win32':
|
||||
if sys.platform != 'win32':
|
||||
# POSIX
|
||||
lang = locale.getlocale()[0]
|
||||
languages = [lang.replace('_', '-')] if lang else []
|
||||
@ -365,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__":
|
||||
|
49
monitor.py
@ -14,7 +14,7 @@ import re
|
||||
import sys
|
||||
import threading
|
||||
from calendar import timegm
|
||||
from collections import OrderedDict, defaultdict
|
||||
from collections import defaultdict
|
||||
from os import SEEK_END, SEEK_SET, listdir
|
||||
from os.path import basename, expanduser, getctime, isdir, join
|
||||
from time import gmtime, localtime, mktime, sleep, strftime, strptime, time
|
||||
@ -34,24 +34,11 @@ 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 == 'darwin':
|
||||
from fcntl import fcntl
|
||||
|
||||
from AppKit import NSWorkspace
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
F_GLOBAL_NOCACHE = 55
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
||||
|
||||
from watchdog.events import FileCreatedEvent, FileSystemEventHandler
|
||||
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
|
||||
@ -63,6 +50,8 @@ elif sys.platform == 'win32':
|
||||
GetWindowText = ctypes.windll.user32.GetWindowTextW
|
||||
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
|
||||
GetWindowTextLength.argtypes = [ctypes.wintypes.HWND]
|
||||
GetWindowTextLength.restype = ctypes.c_int
|
||||
|
||||
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
|
||||
|
||||
@ -71,7 +60,7 @@ else:
|
||||
FileSystemEventHandler = object # dummy
|
||||
if TYPE_CHECKING:
|
||||
# this isn't ever used, but this will make type checking happy
|
||||
from watchdog.events import FileCreatedEvent
|
||||
from watchdog.events import FileSystemEvent
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
|
||||
@ -357,7 +346,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
"""
|
||||
return bool(self.thread and self.thread.is_alive())
|
||||
|
||||
def on_created(self, event: 'FileCreatedEvent') -> None:
|
||||
def on_created(self, event: 'FileSystemEvent') -> None:
|
||||
"""Watchdog callback when, e.g. client (re)started."""
|
||||
if not event.is_directory and self._RE_LOGFILE.search(basename(event.src_path)):
|
||||
|
||||
@ -382,8 +371,6 @@ class EDLogs(FileSystemEventHandler):
|
||||
logfile = self.logfile
|
||||
if logfile:
|
||||
loghandle: BinaryIO = open(logfile, 'rb', 0) # unbuffered
|
||||
if sys.platform == 'darwin':
|
||||
fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB
|
||||
|
||||
self.catching_up = True
|
||||
for line in loghandle:
|
||||
@ -450,7 +437,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
new_journal_file = None
|
||||
|
||||
if logfile:
|
||||
loghandle.seek(0, SEEK_END) # required to make macOS notice log change over SMB
|
||||
loghandle.seek(0, SEEK_END) # required for macOS to notice log change over SMB. TODO: Do we need this?
|
||||
loghandle.seek(log_pos, SEEK_SET) # reset EOF flag # TODO: log_pos reported as possibly unbound
|
||||
for line in loghandle:
|
||||
# Paranoia check to see if we're shutting down
|
||||
@ -483,9 +470,6 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
if logfile:
|
||||
loghandle = open(logfile, 'rb', 0) # unbuffered
|
||||
if sys.platform == 'darwin':
|
||||
fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB
|
||||
|
||||
log_pos = 0
|
||||
|
||||
sleep(self._POLL)
|
||||
@ -567,7 +551,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
try:
|
||||
# Preserve property order because why not?
|
||||
entry: MutableMapping[str, Any] = json.loads(line, object_pairs_hook=OrderedDict)
|
||||
entry: MutableMapping[str, Any] = json.loads(line)
|
||||
assert 'timestamp' in entry, "Timestamp does not exist in the entry"
|
||||
|
||||
self.__navroute_retry()
|
||||
@ -1042,7 +1026,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
rank[k] = (rank[k][0], min(v, 100))
|
||||
|
||||
elif event_type in ('reputation', 'statistics'):
|
||||
payload = OrderedDict(entry)
|
||||
payload = dict(entry)
|
||||
payload.pop('event')
|
||||
payload.pop('timestamp')
|
||||
# NB: We need the original casing for these keys
|
||||
@ -1073,7 +1057,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
# From 3.3 full Cargo event (after the first one) is written to a separate file
|
||||
if 'Inventory' not in entry:
|
||||
with open(join(self.currentdir, 'Cargo.json'), 'rb') as h: # type: ignore
|
||||
entry = json.load(h, object_pairs_hook=OrderedDict) # Preserve property order because why not?
|
||||
entry = json.load(h)
|
||||
self.state['CargoJSON'] = entry
|
||||
|
||||
clean = self.coalesce_cargo(entry['Inventory'])
|
||||
@ -1108,7 +1092,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
attempts += 1
|
||||
try:
|
||||
with open(shiplocker_filename, 'rb') as h:
|
||||
entry = json.load(h, object_pairs_hook=OrderedDict)
|
||||
entry = json.load(h)
|
||||
self.state['ShipLockerJSON'] = entry
|
||||
break
|
||||
|
||||
@ -2144,12 +2128,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
|
||||
:return: bool - True if the game is running.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
for app in NSWorkspace.sharedWorkspace().runningApplications():
|
||||
if app.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
|
||||
return True
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
def WindowTitle(h): # noqa: N802
|
||||
if h:
|
||||
length = GetWindowTextLength(h) + 1
|
||||
@ -2189,7 +2168,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
'PowerDistributor', 'Radar', 'FuelTank'
|
||||
)
|
||||
|
||||
d: MutableMapping[str, Any] = OrderedDict()
|
||||
d: MutableMapping[str, Any] = {}
|
||||
if timestamped:
|
||||
d['timestamp'] = strftime('%Y-%m-%dT%H:%M:%SZ', gmtime())
|
||||
|
||||
|
223
myNotebook.py
@ -1,12 +1,9 @@
|
||||
"""
|
||||
Custom `ttk.Notebook` to fix various display issues.
|
||||
|
||||
Hacks to fix various display issues with notebooks and their child widgets on
|
||||
OSX and Windows.
|
||||
Hacks to fix various display issues with notebooks and their child widgets on Windows.
|
||||
|
||||
- Windows: page background should be White, not SystemButtonFace
|
||||
- OSX: page background should be a darker gray than systemWindowBody
|
||||
selected tab foreground should be White when the window is active
|
||||
|
||||
Entire file may be imported by plugins.
|
||||
"""
|
||||
@ -14,15 +11,11 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import ttk, messagebox
|
||||
from PIL import ImageGrab
|
||||
from l10n import translations as tr
|
||||
|
||||
# Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult
|
||||
if sys.platform == 'darwin':
|
||||
from platform import mac_ver
|
||||
PAGEFG = 'systemButtonText'
|
||||
PAGEBG = 'systemButtonActiveDarkShadow'
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
PAGEFG = 'SystemWindowText'
|
||||
PAGEBG = 'SystemWindow' # typically white
|
||||
|
||||
@ -32,39 +25,22 @@ class Notebook(ttk.Notebook):
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
|
||||
ttk.Notebook.__init__(self, master, **kw)
|
||||
super().__init__(master, **kw)
|
||||
style = ttk.Style()
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
if list(map(int, mac_ver()[0].split('.'))) >= [10, 10]:
|
||||
# Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see
|
||||
# https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25
|
||||
style.configure('TNotebook.Tab', padding=(12, 10, 12, 2))
|
||||
style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')])
|
||||
self.grid(sticky=tk.NSEW) # Already padded apropriately
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
style.configure('nb.TFrame', background=PAGEBG)
|
||||
style.configure('nb.TButton', background=PAGEBG)
|
||||
style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
|
||||
style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG)
|
||||
style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
|
||||
self.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||
else:
|
||||
self.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||
self.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||
|
||||
|
||||
# FIXME: The real fix for this 'dynamic type' would be to split this whole
|
||||
# thing into being a module with per-platform files, as we've done with config
|
||||
# That would also make the code cleaner.
|
||||
class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame): # type: ignore
|
||||
"""Custom t(t)k.Frame class to fix some display issues."""
|
||||
class Frame(ttk.Frame):
|
||||
"""Custom ttk.Frame class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Notebook | None = None, **kw):
|
||||
if sys.platform == 'darwin':
|
||||
kw['background'] = kw.pop('background', PAGEBG)
|
||||
tk.Frame.__init__(self, master, **kw)
|
||||
tk.Frame(self).grid(pady=5)
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
ttk.Frame.__init__(self, master, style='nb.TFrame', **kw)
|
||||
ttk.Frame(self).grid(pady=5) # top spacer
|
||||
else:
|
||||
@ -77,105 +53,128 @@ class Label(tk.Label):
|
||||
"""Custom tk.Label class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
# This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
|
||||
if sys.platform in ('darwin', 'win32'):
|
||||
kw['foreground'] = kw.pop('foreground', PAGEFG)
|
||||
kw['background'] = kw.pop('background', PAGEBG)
|
||||
else:
|
||||
kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground'))
|
||||
kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background'))
|
||||
tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms
|
||||
kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32'
|
||||
else ttk.Style().lookup('TLabel', 'foreground'))
|
||||
kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32'
|
||||
else ttk.Style().lookup('TLabel', 'background'))
|
||||
super().__init__(master, **kw)
|
||||
|
||||
|
||||
class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore
|
||||
"""Custom t(t)k.Entry class to fix some display issues."""
|
||||
class EntryMenu(ttk.Entry):
|
||||
"""Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
ttk.Entry.__init__(self, *args, **kwargs)
|
||||
|
||||
self.menu = tk.Menu(self, tearoff=False)
|
||||
self.menu.add_command(label=tr.tl("Copy"), command=self.copy) # LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||
self.menu.add_command(label=tr.tl("Cut"), command=self.cut) # LANG: Label for 'Cut' as in 'Cut and Paste'
|
||||
self.menu.add_separator()
|
||||
# LANG: Label for 'Paste' as in 'Copy and Paste'
|
||||
self.menu.add_command(label=tr.tl("Paste"), command=self.paste)
|
||||
self.menu.add_separator()
|
||||
# LANG: Label for 'Select All'
|
||||
self.menu.add_command(label=tr.tl("Select All"), command=self.select_all)
|
||||
|
||||
self.bind("<Button-3>", self.display_popup)
|
||||
|
||||
def display_popup(self, event: tk.Event) -> None:
|
||||
"""Display the menu popup."""
|
||||
self.menu.post(event.x_root, event.y_root)
|
||||
|
||||
def select_all(self) -> None:
|
||||
"""Select all the text within the Entry."""
|
||||
self.selection_range(0, tk.END)
|
||||
self.focus_set()
|
||||
|
||||
def copy(self) -> None:
|
||||
"""Copy the selected Entry text."""
|
||||
if self.selection_present():
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(self.selection_get())
|
||||
|
||||
def cut(self) -> None:
|
||||
"""Cut the selected Entry text."""
|
||||
if self.selection_present():
|
||||
self.copy()
|
||||
self.delete(tk.SEL_FIRST, tk.SEL_LAST)
|
||||
|
||||
def paste(self) -> None:
|
||||
"""Paste the selected Entry text."""
|
||||
try:
|
||||
# Attempt to grab an image from the clipboard (apprently also works for files)
|
||||
img = ImageGrab.grabclipboard()
|
||||
if img:
|
||||
# Hijack existing translation, yes it doesn't exactly match here.
|
||||
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
|
||||
parent=self.master
|
||||
)
|
||||
return
|
||||
text = self.clipboard_get()
|
||||
if self.selection_present() and text:
|
||||
self.delete(tk.SEL_FIRST, tk.SEL_LAST)
|
||||
self.insert(tk.INSERT, text)
|
||||
except tk.TclError:
|
||||
# No text in clipboard or clipboard is not text
|
||||
pass
|
||||
|
||||
|
||||
class Entry(EntryMenu):
|
||||
"""Custom ttk.Entry class to fix some display issues."""
|
||||
|
||||
# DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later.
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
EntryMenu.__init__(self, master, **kw)
|
||||
|
||||
|
||||
class Button(ttk.Button):
|
||||
"""Custom ttk.Button class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
if sys.platform == 'darwin':
|
||||
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
|
||||
tk.Entry.__init__(self, master, **kw)
|
||||
else:
|
||||
ttk.Entry.__init__(self, master, **kw)
|
||||
|
||||
|
||||
class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ignore
|
||||
"""Custom t(t)k.Button class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
if sys.platform == 'darwin':
|
||||
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
|
||||
tk.Button.__init__(self, master, **kw)
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
ttk.Button.__init__(self, master, style='nb.TButton', **kw)
|
||||
else:
|
||||
ttk.Button.__init__(self, master, **kw)
|
||||
|
||||
|
||||
class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): # type: ignore
|
||||
"""Custom t(t)k.ColoredButton class to fix some display issues."""
|
||||
class ColoredButton(tk.Button):
|
||||
"""Custom tk.Button class to fix some display issues."""
|
||||
|
||||
# DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later.
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
tk.Button.__init__(self, master, **kw)
|
||||
|
||||
|
||||
class Checkbutton(ttk.Checkbutton):
|
||||
"""Custom ttk.Checkbutton class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
if sys.platform == 'darwin':
|
||||
# Can't set Button background on OSX, so use a Label instead
|
||||
kw['relief'] = kw.pop('relief', tk.RAISED)
|
||||
self._command = kw.pop('command', None)
|
||||
tk.Label.__init__(self, master, **kw)
|
||||
self.bind('<Button-1>', self._press)
|
||||
else:
|
||||
tk.Button.__init__(self, master, **kw)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
def _press(self, event):
|
||||
self._command()
|
||||
style = 'nb.TCheckbutton' if sys.platform == 'win32' else None
|
||||
super().__init__(master, style=style, **kw) # type: ignore
|
||||
|
||||
|
||||
class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): # type: ignore
|
||||
"""Custom t(t)k.Checkbutton class to fix some display issues."""
|
||||
class Radiobutton(ttk.Radiobutton):
|
||||
"""Custom ttk.Radiobutton class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
if sys.platform == 'darwin':
|
||||
kw['foreground'] = kw.pop('foreground', PAGEFG)
|
||||
kw['background'] = kw.pop('background', PAGEBG)
|
||||
tk.Checkbutton.__init__(self, master, **kw)
|
||||
elif sys.platform == 'win32':
|
||||
ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw)
|
||||
else:
|
||||
ttk.Checkbutton.__init__(self, master, **kw)
|
||||
style = 'nb.TRadiobutton' if sys.platform == 'win32' else None
|
||||
super().__init__(master, style=style, **kw) # type: ignore
|
||||
|
||||
|
||||
class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton): # type: ignore
|
||||
"""Custom t(t)k.Radiobutton class to fix some display issues."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
if sys.platform == 'darwin':
|
||||
kw['foreground'] = kw.pop('foreground', PAGEFG)
|
||||
kw['background'] = kw.pop('background', PAGEBG)
|
||||
tk.Radiobutton.__init__(self, master, **kw)
|
||||
elif sys.platform == 'win32':
|
||||
ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw)
|
||||
else:
|
||||
ttk.Radiobutton.__init__(self, master, **kw)
|
||||
|
||||
|
||||
class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu): # type: ignore
|
||||
"""Custom t(t)k.OptionMenu class to fix some display issues."""
|
||||
class OptionMenu(ttk.OptionMenu):
|
||||
"""Custom ttk.OptionMenu class to fix some display issues."""
|
||||
|
||||
def __init__(self, master, variable, default=None, *values, **kw):
|
||||
if sys.platform == 'darwin':
|
||||
variable.set(default)
|
||||
bg = kw.pop('background', PAGEBG)
|
||||
tk.OptionMenu.__init__(self, master, variable, *values, **kw)
|
||||
self['background'] = bg
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
# OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
|
||||
ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
|
||||
self['menu'].configure(background=PAGEBG)
|
||||
# Workaround for https://bugs.python.org/issue25684
|
||||
for i in range(0, self['menu'].index('end')+1):
|
||||
self['menu'].entryconfig(i, variable=variable)
|
||||
else:
|
||||
ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
|
||||
self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
|
||||
# Workaround for https://bugs.python.org/issue25684
|
||||
for i in range(0, self['menu'].index('end')+1):
|
||||
self['menu'].entryconfig(i, variable=variable)
|
||||
|
||||
# Workaround for https://bugs.python.org/issue25684
|
||||
for i in range(0, self['menu'].index('end') + 1):
|
||||
self['menu'].entryconfig(i, variable=variable)
|
||||
|
@ -8,8 +8,6 @@ See LICENSE file.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from typing import OrderedDict as OrderedDictT
|
||||
from config import config
|
||||
from edmc_data import (
|
||||
outfitting_armour_map as armour_map,
|
||||
@ -36,7 +34,7 @@ from EDMCLogging import get_main_logger
|
||||
logger = get_main_logger()
|
||||
|
||||
# Module mass, FSD data etc
|
||||
moduledata: OrderedDictT = OrderedDict()
|
||||
moduledata: dict = {}
|
||||
|
||||
|
||||
def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR001
|
||||
|
38
plug.py
@ -47,14 +47,14 @@ last_error = LastError()
|
||||
class Plugin:
|
||||
"""An EDMC plugin."""
|
||||
|
||||
def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logger | None):
|
||||
def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logger | None): # noqa: CCR001
|
||||
"""
|
||||
Load a single plugin.
|
||||
|
||||
:param name: Base name of the file being loaded from.
|
||||
:param loadfile: Full path/filename of the plugin.
|
||||
:param plugin_logger: The logging instance for this plugin to use.
|
||||
:raises Exception: Typically ImportError or OSError
|
||||
:raises Exception: Typically, ImportError or OSError
|
||||
"""
|
||||
self.name: str = name # Display name.
|
||||
self.folder: str | None = name # basename of plugin folder. None for internal plugins.
|
||||
@ -66,19 +66,23 @@ class Plugin:
|
||||
try:
|
||||
filename = 'plugin_'
|
||||
filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_')
|
||||
module = importlib.machinery.SourceFileLoader(
|
||||
filename,
|
||||
loadfile
|
||||
).load_module()
|
||||
if getattr(module, 'plugin_start3', None):
|
||||
newname = module.plugin_start3(os.path.dirname(loadfile))
|
||||
self.name = str(newname) if newname else self.name
|
||||
self.module = module
|
||||
elif getattr(module, 'plugin_start', None):
|
||||
logger.warning(f'plugin {name} needs migrating\n')
|
||||
PLUGINS_not_py3.append(self)
|
||||
spec = importlib.util.spec_from_file_location(filename, loadfile)
|
||||
# Replaces older load_module() code. Includes a safety check that the module name is set.
|
||||
if spec is not None and spec.loader is not None:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module.__name__] = module
|
||||
spec.loader.exec_module(module)
|
||||
if getattr(module, 'plugin_start3', None):
|
||||
newname = module.plugin_start3(os.path.dirname(loadfile))
|
||||
self.name = str(newname) if newname else self.name
|
||||
self.module = module
|
||||
elif getattr(module, 'plugin_start', None):
|
||||
logger.warning(f'plugin {name} needs migrating\n')
|
||||
PLUGINS_not_py3.append(self)
|
||||
else:
|
||||
logger.error(f'plugin {name} has no plugin_start3() function')
|
||||
else:
|
||||
logger.error(f'plugin {name} has no plugin_start3() function')
|
||||
logger.error(f'Failed to load Plugin "{name}" from file "{loadfile}"')
|
||||
except Exception:
|
||||
logger.exception(f': Failed for Plugin "{name}"')
|
||||
raise
|
||||
@ -126,7 +130,7 @@ class Plugin:
|
||||
|
||||
return None
|
||||
|
||||
def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame | None:
|
||||
def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame | None:
|
||||
"""
|
||||
If the plugin provides a prefs frame, create and return it.
|
||||
|
||||
@ -414,7 +418,7 @@ def notify_capidata(data: companion.CAPIData, is_beta: bool) -> str | None:
|
||||
|
||||
def notify_capi_fleetcarrierdata(data: companion.CAPIData) -> str | None:
|
||||
"""
|
||||
Send the latest CAPI Fleetcarrier data from the FD servers to each plugin.
|
||||
Send the latest CAPI Fleet Carrier data from the FD servers to each plugin.
|
||||
|
||||
:param data: The CAPIData returned in the CAPI response
|
||||
:returns: Error message from the first plugin that returns one (if any)
|
||||
@ -429,7 +433,7 @@ def notify_capi_fleetcarrierdata(data: companion.CAPIData) -> str | None:
|
||||
error = error if error else newerror
|
||||
|
||||
except Exception:
|
||||
logger.exception(f'Plugin "{plugin.name}" failed on receiving Fleetcarrier data')
|
||||
logger.exception(f'Plugin "{plugin.name}" failed on receiving Fleet Carrier data')
|
||||
|
||||
return error
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -84,46 +80,45 @@ def plugin_start3(path: str) -> str:
|
||||
return 'Coriolis'
|
||||
|
||||
|
||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
|
||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame:
|
||||
"""Set up plugin preferences."""
|
||||
PADX = 10 # noqa: N806
|
||||
PADY = 1 # noqa: N806
|
||||
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.Entry(conf_frame,
|
||||
textvariable=coriolis_config.normal_textvar).grid(
|
||||
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.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
|
||||
sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
|
||||
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
|
||||
)
|
||||
@ -131,16 +126,16 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.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
|
||||
|
||||
@ -160,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
|
||||
@ -176,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)
|
||||
@ -197,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':
|
||||
|
203
plugins/eddn.py
@ -27,20 +27,11 @@ import os
|
||||
import pathlib
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from collections import OrderedDict
|
||||
from platform import system
|
||||
from textwrap import dedent
|
||||
from threading import Lock
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Iterator,
|
||||
Mapping,
|
||||
MutableMapping,
|
||||
)
|
||||
from typing import OrderedDict as OrderedDictT
|
||||
from typing import Any, Iterator, Mapping, MutableMapping
|
||||
import requests
|
||||
import companion
|
||||
import edmc_data
|
||||
@ -55,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()
|
||||
|
||||
@ -98,13 +86,13 @@ class This:
|
||||
|
||||
# Avoid duplicates
|
||||
self.marketId: str | None = None
|
||||
self.commodities: list[OrderedDictT[str, Any]] | None = None
|
||||
self.commodities: list[dict[str, Any]] | None = None
|
||||
self.outfitting: tuple[bool, list[str]] | None = None
|
||||
self.shipyard: tuple[bool, list[Mapping[str, Any]]] | None = None
|
||||
self.fcmaterials_marketid: int = 0
|
||||
self.fcmaterials: list[OrderedDictT[str, Any]] | None = None
|
||||
self.fcmaterials: list[dict[str, Any]] | None = None
|
||||
self.fcmaterials_capi_marketid: int = 0
|
||||
self.fcmaterials_capi: list[OrderedDictT[str, Any]] | None = None
|
||||
self.fcmaterials_capi: list[dict[str, Any]] | None = None
|
||||
|
||||
# For the tkinter parent window, so we can call update_idletasks()
|
||||
self.parent: tk.Tk
|
||||
@ -284,7 +272,7 @@ class EDDNSender:
|
||||
msg['header'] = {
|
||||
# We have to lie and say it's *this* version, but denote that
|
||||
# it might not actually be this version.
|
||||
'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]'
|
||||
'softwareName': f'{applongname} [{system()}]'
|
||||
' (legacy replay)',
|
||||
'softwareVersion': str(appversion_nobuild()),
|
||||
'uploaderID': cmdr,
|
||||
@ -444,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)
|
||||
@ -569,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.
|
||||
@ -651,21 +639,21 @@ class EDDN:
|
||||
modules,
|
||||
ships
|
||||
)
|
||||
commodities: list[OrderedDictT[str, Any]] = []
|
||||
commodities: list[dict[str, Any]] = []
|
||||
for commodity in data['lastStarport'].get('commodities') or []:
|
||||
# Check 'marketable' and 'not prohibited'
|
||||
if (category_map.get(commodity['categoryname'], True)
|
||||
and not commodity.get('legality')):
|
||||
commodities.append(OrderedDict([
|
||||
('name', commodity['name'].lower()),
|
||||
('meanPrice', int(commodity['meanPrice'])),
|
||||
('buyPrice', int(commodity['buyPrice'])),
|
||||
('stock', int(commodity['stock'])),
|
||||
('stockBracket', commodity['stockBracket']),
|
||||
('sellPrice', int(commodity['sellPrice'])),
|
||||
('demand', int(commodity['demand'])),
|
||||
('demandBracket', commodity['demandBracket']),
|
||||
]))
|
||||
commodities.append({
|
||||
'name': commodity['name'].lower(),
|
||||
'meanPrice': int(commodity['meanPrice']),
|
||||
'buyPrice': int(commodity['buyPrice']),
|
||||
'stock': int(commodity['stock']),
|
||||
'stockBracket': commodity['stockBracket'],
|
||||
'sellPrice': int(commodity['sellPrice']),
|
||||
'demand': int(commodity['demand']),
|
||||
'demandBracket': commodity['demandBracket'],
|
||||
})
|
||||
|
||||
if commodity['statusFlags']:
|
||||
commodities[-1]['statusFlags'] = commodity['statusFlags']
|
||||
@ -679,15 +667,15 @@ class EDDN:
|
||||
# none and that really does need to be recorded over EDDN so that
|
||||
# tools can update in a timely manner.
|
||||
if this.commodities != commodities:
|
||||
message: OrderedDictT[str, Any] = OrderedDict([
|
||||
('timestamp', data['timestamp']),
|
||||
('systemName', data['lastSystem']['name']),
|
||||
('stationName', data['lastStarport']['name']),
|
||||
('marketId', data['lastStarport']['id']),
|
||||
('commodities', commodities),
|
||||
('horizons', horizons),
|
||||
('odyssey', this.odyssey),
|
||||
])
|
||||
message: dict[str, Any] = {
|
||||
'timestamp': data['timestamp'],
|
||||
'systemName': data['lastSystem']['name'],
|
||||
'stationName': data['lastStarport']['name'],
|
||||
'marketId': data['lastStarport']['id'],
|
||||
'commodities': commodities,
|
||||
'horizons': horizons,
|
||||
'odyssey': this.odyssey,
|
||||
}
|
||||
|
||||
if 'economies' in data['lastStarport']:
|
||||
message['economies'] = sorted(
|
||||
@ -802,16 +790,16 @@ class EDDN:
|
||||
if outfitting and this.outfitting != (horizons, outfitting):
|
||||
self.send_message(data['commander']['name'], {
|
||||
'$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}',
|
||||
'message': OrderedDict([
|
||||
('timestamp', data['timestamp']),
|
||||
('systemName', data['lastSystem']['name']),
|
||||
('stationName', data['lastStarport']['name']),
|
||||
('marketId', data['lastStarport']['id']),
|
||||
('horizons', horizons),
|
||||
('modules', outfitting),
|
||||
('odyssey', this.odyssey),
|
||||
]),
|
||||
'header': self.standard_header(
|
||||
'message': {
|
||||
'timestamp': data['timestamp'],
|
||||
'systemName': data['lastSystem']['name'],
|
||||
'stationName': data['lastStarport']['name'],
|
||||
'marketId': data['lastStarport']['id'],
|
||||
'horizons': horizons,
|
||||
'modules': outfitting,
|
||||
'odyssey': this.odyssey,
|
||||
},
|
||||
'header': self.standard_header(
|
||||
game_version=self.capi_gameversion_from_host_endpoint(
|
||||
data.source_host, companion.Session.FRONTIER_CAPI_PATH_SHIPYARD
|
||||
),
|
||||
@ -864,15 +852,15 @@ class EDDN:
|
||||
if shipyard and this.shipyard != (horizons, shipyard):
|
||||
self.send_message(data['commander']['name'], {
|
||||
'$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}',
|
||||
'message': OrderedDict([
|
||||
('timestamp', data['timestamp']),
|
||||
('systemName', data['lastSystem']['name']),
|
||||
('stationName', data['lastStarport']['name']),
|
||||
('marketId', data['lastStarport']['id']),
|
||||
('horizons', horizons),
|
||||
('ships', shipyard),
|
||||
('odyssey', this.odyssey),
|
||||
]),
|
||||
'message': {
|
||||
'timestamp': data['timestamp'],
|
||||
'systemName': data['lastSystem']['name'],
|
||||
'stationName': data['lastStarport']['name'],
|
||||
'marketId': data['lastStarport']['id'],
|
||||
'horizons': horizons,
|
||||
'ships': shipyard,
|
||||
'odyssey': this.odyssey,
|
||||
},
|
||||
'header': self.standard_header(
|
||||
game_version=self.capi_gameversion_from_host_endpoint(
|
||||
data.source_host, companion.Session.FRONTIER_CAPI_PATH_SHIPYARD
|
||||
@ -898,16 +886,22 @@ class EDDN:
|
||||
:param entry: the journal entry containing the commodities data
|
||||
"""
|
||||
items: list[Mapping[str, Any]] = entry.get('Items') or []
|
||||
commodities: list[OrderedDictT[str, Any]] = sorted((OrderedDict([
|
||||
('name', self.canonicalise(commodity['Name'])),
|
||||
('meanPrice', commodity['MeanPrice']),
|
||||
('buyPrice', commodity['BuyPrice']),
|
||||
('stock', commodity['Stock']),
|
||||
('stockBracket', commodity['StockBracket']),
|
||||
('sellPrice', commodity['SellPrice']),
|
||||
('demand', commodity['Demand']),
|
||||
('demandBracket', commodity['DemandBracket']),
|
||||
]) for commodity in items), key=lambda c: c['name'])
|
||||
commodities: list[dict[str, Any]] = sorted(
|
||||
(
|
||||
{
|
||||
'name': self.canonicalise(commodity['Name']),
|
||||
'meanPrice': commodity['MeanPrice'],
|
||||
'buyPrice': commodity['BuyPrice'],
|
||||
'stock': commodity['Stock'],
|
||||
'stockBracket': commodity['StockBracket'],
|
||||
'sellPrice': commodity['SellPrice'],
|
||||
'demand': commodity['Demand'],
|
||||
'demandBracket': commodity['DemandBracket'],
|
||||
}
|
||||
for commodity in items
|
||||
),
|
||||
key=lambda c: c['name']
|
||||
)
|
||||
|
||||
# This used to have a check `commodities and ` at the start so as to
|
||||
# not send an empty commodities list, as the EDDN Schema doesn't allow
|
||||
@ -916,18 +910,24 @@ class EDDN:
|
||||
# none and that really does need to be recorded over EDDN so that
|
||||
# tools can update in a timely manner.
|
||||
if this.commodities != commodities:
|
||||
self.send_message(cmdr, {
|
||||
message: dict[str, Any] = { # Yes, this is a broad type hint.
|
||||
'$schemaRef': f'https://eddn.edcd.io/schemas/commodity/3{"/test" if is_beta else ""}',
|
||||
'message': OrderedDict([
|
||||
('timestamp', entry['timestamp']),
|
||||
('systemName', entry['StarSystem']),
|
||||
('stationName', entry['StationName']),
|
||||
('marketId', entry['MarketID']),
|
||||
('commodities', commodities),
|
||||
('horizons', this.horizons),
|
||||
('odyssey', this.odyssey),
|
||||
]),
|
||||
})
|
||||
'message': {
|
||||
'timestamp': entry['timestamp'],
|
||||
'systemName': entry['StarSystem'],
|
||||
'stationName': entry['StationName'],
|
||||
'marketId': entry['MarketID'],
|
||||
'commodities': commodities,
|
||||
'horizons': this.horizons,
|
||||
'odyssey': this.odyssey,
|
||||
'stationType': entry['StationType'],
|
||||
}
|
||||
}
|
||||
|
||||
if entry.get('CarrierDockingAccess'):
|
||||
message['message']['carrierDockingAccess'] = entry['CarrierDockingAccess']
|
||||
|
||||
self.send_message(cmdr, message)
|
||||
|
||||
this.commodities = commodities
|
||||
|
||||
@ -957,7 +957,7 @@ class EDDN:
|
||||
if outfitting and this.outfitting != (horizons, outfitting):
|
||||
self.send_message(cmdr, {
|
||||
'$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}',
|
||||
'message': OrderedDict([
|
||||
'message': {
|
||||
('timestamp', entry['timestamp']),
|
||||
('systemName', entry['StarSystem']),
|
||||
('stationName', entry['StationName']),
|
||||
@ -965,7 +965,7 @@ class EDDN:
|
||||
('horizons', horizons),
|
||||
('modules', outfitting),
|
||||
('odyssey', entry['odyssey'])
|
||||
]),
|
||||
},
|
||||
})
|
||||
|
||||
this.outfitting = (horizons, outfitting)
|
||||
@ -991,7 +991,7 @@ class EDDN:
|
||||
if shipyard and this.shipyard != (horizons, shipyard):
|
||||
self.send_message(cmdr, {
|
||||
'$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}',
|
||||
'message': OrderedDict([
|
||||
'message': {
|
||||
('timestamp', entry['timestamp']),
|
||||
('systemName', entry['StarSystem']),
|
||||
('stationName', entry['StationName']),
|
||||
@ -999,7 +999,7 @@ class EDDN:
|
||||
('horizons', horizons),
|
||||
('ships', shipyard),
|
||||
('odyssey', entry['odyssey'])
|
||||
]),
|
||||
},
|
||||
})
|
||||
|
||||
# this.shipyard = (horizons, shipyard)
|
||||
@ -1064,7 +1064,7 @@ class EDDN:
|
||||
gb = this.game_build
|
||||
|
||||
return {
|
||||
'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]',
|
||||
'softwareName': f'{applongname} [{system()}]',
|
||||
'softwareVersion': str(appversion_nobuild()),
|
||||
'uploaderID': this.cmdr_name,
|
||||
'gameversion': gv,
|
||||
@ -1889,7 +1889,7 @@ class EDDN:
|
||||
:param cmdr: the commander under which this upload is made
|
||||
:param is_beta: whether or not we are in beta mode
|
||||
:param entry: the journal entry to send
|
||||
|
||||
___
|
||||
Example:
|
||||
{
|
||||
"timestamp":"2022-06-10T10:09:41Z",
|
||||
@ -1923,7 +1923,7 @@ class EDDN:
|
||||
:param cmdr: the commander under which this upload is made
|
||||
:param is_beta: whether or not we are in beta mode
|
||||
:param entry: the journal entry to send
|
||||
|
||||
___
|
||||
Example:
|
||||
{
|
||||
"timestamp":"2023-10-01T14:56:34Z",
|
||||
@ -2180,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
|
||||
@ -2192,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
|
||||
)
|
||||
@ -2204,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)
|
||||
@ -2250,14 +2250,14 @@ def plugin_stop() -> None:
|
||||
logger.debug('Done.')
|
||||
|
||||
|
||||
def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]:
|
||||
def filter_localised(d: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Recursively remove any dict keys with names ending `_Localised` from a dict.
|
||||
|
||||
:param d: dict to filter keys of.
|
||||
:return: The filtered dict.
|
||||
"""
|
||||
filtered: OrderedDictT[str, Any] = OrderedDict()
|
||||
filtered: dict[str, Any] = {}
|
||||
for k, v in d.items():
|
||||
if k.endswith('_Localised'):
|
||||
pass
|
||||
@ -2274,14 +2274,14 @@ def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]:
|
||||
return filtered
|
||||
|
||||
|
||||
def capi_filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]:
|
||||
def capi_filter_localised(d: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Recursively remove any dict keys for known CAPI 'localised' names.
|
||||
|
||||
:param d: dict to filter keys of.
|
||||
:return: The filtered dict.
|
||||
"""
|
||||
filtered: OrderedDictT[str, Any] = OrderedDict()
|
||||
filtered: dict[str, Any] = {}
|
||||
for k, v in d.items():
|
||||
if EDDN.CAPI_LOCALISATION_RE.search(k):
|
||||
pass
|
||||
@ -2319,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)
|
||||
@ -2521,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)
|
||||
@ -2559,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)
|
||||
@ -2613,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)
|
||||
@ -2625,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)
|
||||
|
185
plugins/edsm.py
@ -28,7 +28,7 @@ from queue import Queue
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Any, Literal, Mapping, MutableMapping, cast
|
||||
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.
|
||||
@ -113,10 +111,10 @@ class This:
|
||||
self.cmdr_text: nb.Label | None = None
|
||||
|
||||
self.user_label: nb.Label | None = None
|
||||
self.user: nb.Entry | None = None
|
||||
self.user: nb.EntryMenu | None = None
|
||||
|
||||
self.apikey_label: nb.Label | None = None
|
||||
self.apikey: nb.Entry | None = None
|
||||
self.apikey: nb.EntryMenu | None = None
|
||||
|
||||
|
||||
this = This()
|
||||
@ -279,7 +277,7 @@ def toggle_password_visibility():
|
||||
this.apikey.config(show="*") # type: ignore
|
||||
|
||||
|
||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
|
||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame:
|
||||
"""
|
||||
Plugin preferences setup hook.
|
||||
|
||||
@ -313,7 +311,8 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.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) -> tk.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,23 +335,23 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.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.Entry(frame)
|
||||
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.Entry(frame, show="*", width=50)
|
||||
this.apikey = nb.EntryMenu(frame, show="*", width=50)
|
||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
cur_row += 1
|
||||
|
||||
@ -362,7 +361,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.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():
|
||||
@ -492,6 +491,20 @@ def credentials(cmdr: str) -> tuple[str, str] | None:
|
||||
edsm_usernames = config.get_list('edsm_usernames')
|
||||
edsm_apikeys = config.get_list('edsm_apikeys')
|
||||
|
||||
if not edsm_usernames: # https://github.com/EDCD/EDMarketConnector/issues/2232
|
||||
edsm_usernames = ["" for _ in range(len(cmdrs))]
|
||||
else: # Check for Mismatched Length - fill with null values.
|
||||
if len(edsm_usernames) < len(cmdrs):
|
||||
edsm_usernames.extend(["" for _ in range(len(cmdrs) - len(edsm_usernames))])
|
||||
config.set('edsm_usernames', edsm_usernames)
|
||||
|
||||
if not edsm_apikeys:
|
||||
edsm_apikeys = ["" for _ in range(len(cmdrs))]
|
||||
else: # Check for Mismatched Length - fill with null values.
|
||||
if len(edsm_apikeys) < len(cmdrs):
|
||||
edsm_apikeys.extend(["" for _ in range(len(cmdrs) - len(edsm_apikeys))])
|
||||
config.set('edsm_apikeys', edsm_apikeys)
|
||||
|
||||
if cmdr in cmdrs and len(cmdrs) == len(edsm_usernames) == len(edsm_apikeys):
|
||||
idx = cmdrs.index(cmdr)
|
||||
if idx < len(edsm_usernames) and idx < len(edsm_apikeys):
|
||||
@ -519,7 +532,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 +619,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 ''
|
||||
|
||||
@ -722,6 +735,87 @@ def get_discarded_events_list() -> None:
|
||||
logger.warning('Exception while trying to set this.discarded_events:', exc_info=e)
|
||||
|
||||
|
||||
def process_discarded_events() -> None:
|
||||
"""Process discarded events until the discarded events list is retrieved or the shutdown signal is received."""
|
||||
while not this.discarded_events:
|
||||
if this.shutting_down:
|
||||
logger.debug(f'returning from discarded_events loop due to {this.shutting_down=}')
|
||||
return
|
||||
get_discarded_events_list()
|
||||
if this.discarded_events:
|
||||
break
|
||||
sleep(DISCARDED_EVENTS_SLEEP)
|
||||
|
||||
logger.debug('Got "events to discard" list, commencing queue consumption...')
|
||||
|
||||
|
||||
def send_to_edsm( # noqa: CCR001
|
||||
data: dict[str, Sequence[object]], pending: list[Mapping[str, Any]], closing: bool
|
||||
) -> list[Mapping[str, Any]]:
|
||||
"""Send data to the EDSM API endpoint and handle the API response."""
|
||||
response = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT)
|
||||
logger.trace_if('plugin.edsm.api', f'API response content: {response.content!r}')
|
||||
|
||||
# Check for rate limit headers
|
||||
rate_limit_remaining = response.headers.get('X-Rate-Limit-Remaining')
|
||||
rate_limit_reset = response.headers.get('X-Rate-Limit-Reset')
|
||||
|
||||
# Convert headers to integers if they exist
|
||||
try:
|
||||
remaining = int(rate_limit_remaining) if rate_limit_remaining else None
|
||||
reset = int(rate_limit_reset) if rate_limit_reset else None
|
||||
except ValueError:
|
||||
remaining = reset = None
|
||||
|
||||
if remaining is not None and reset is not None:
|
||||
# Respect rate limits if they exist
|
||||
if remaining == 0:
|
||||
# Calculate sleep time until the rate limit reset time
|
||||
reset_time = datetime.utcfromtimestamp(reset)
|
||||
current_time = datetime.utcnow()
|
||||
|
||||
sleep_time = (reset_time - current_time).total_seconds()
|
||||
|
||||
if sleep_time > 0:
|
||||
sleep(sleep_time)
|
||||
|
||||
response.raise_for_status()
|
||||
reply = response.json()
|
||||
msg_num = reply['msgnum']
|
||||
msg = reply['msg']
|
||||
# 1xx = OK
|
||||
# 2xx = fatal error
|
||||
# 3&4xx not generated at top-level
|
||||
# 5xx = error but events saved for later processing
|
||||
|
||||
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(tr.tl('Error: EDSM {MSG}').format(MSG=msg))
|
||||
else:
|
||||
if msg_num // 100 == 1:
|
||||
logger.trace_if('plugin.edsm.api', 'Overall OK')
|
||||
pass
|
||||
elif msg_num // 100 == 5:
|
||||
logger.trace_if('plugin.edsm.api', 'Event(s) not currently processed, but saved for later')
|
||||
pass
|
||||
else:
|
||||
logger.warning(f'EDSM API call status not 1XX, 2XX or 5XX: {msg.num}')
|
||||
|
||||
for e, r in zip(pending, reply['events']):
|
||||
if not closing and e['event'] in ('StartUp', 'Location', 'FSDJump', 'CarrierJump'):
|
||||
# Update main window's system status
|
||||
this.lastlookup = r
|
||||
# calls update_status in main thread
|
||||
if not config.shutting_down and this.system_link is not None:
|
||||
this.system_link.event_generate('<<EDSMStatus>>', when="tail")
|
||||
if r['msgnum'] // 100 != 1:
|
||||
logger.warning(f'EDSM event with not-1xx status:\n{r["msgnum"]}\n'
|
||||
f'{r["msg"]}\n{json.dumps(e, separators=(",", ": "))}')
|
||||
pending = []
|
||||
return pending
|
||||
|
||||
|
||||
def worker() -> None: # noqa: CCR001 C901
|
||||
"""
|
||||
Handle uploading events to EDSM API.
|
||||
@ -738,17 +832,9 @@ def worker() -> None: # noqa: CCR001 C901
|
||||
last_game_version = ""
|
||||
last_game_build = ""
|
||||
|
||||
while not this.discarded_events:
|
||||
if this.shutting_down:
|
||||
logger.debug(f'returning from discarded_events loop due to {this.shutting_down=}')
|
||||
return
|
||||
get_discarded_events_list()
|
||||
if this.discarded_events:
|
||||
break
|
||||
# Process the Discard Queue
|
||||
process_discarded_events()
|
||||
|
||||
sleep(DISCARDED_EVENTS_SLEEP)
|
||||
|
||||
logger.debug('Got "events to discard" list, commencing queue consumption...')
|
||||
while True:
|
||||
if this.shutting_down:
|
||||
logger.debug(f'{this.shutting_down=}, so setting closing = True')
|
||||
@ -861,43 +947,8 @@ def worker() -> None: # noqa: CCR001 C901
|
||||
'journal.locations', f'Overall POST data (elided) is:\n{json.dumps(data_elided, indent=2)}'
|
||||
)
|
||||
|
||||
response = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT)
|
||||
logger.trace_if('plugin.edsm.api', f'API response content: {response.content!r}')
|
||||
response.raise_for_status()
|
||||
pending = send_to_edsm(data, pending, closing)
|
||||
|
||||
reply = response.json()
|
||||
msg_num = reply['msgnum']
|
||||
msg = reply['msg']
|
||||
# 1xx = OK
|
||||
# 2xx = fatal error
|
||||
# 3&4xx not generated at top-level
|
||||
# 5xx = error but events saved for later processing
|
||||
|
||||
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))
|
||||
else:
|
||||
if msg_num // 100 == 1:
|
||||
logger.trace_if('plugin.edsm.api', 'Overall OK')
|
||||
pass
|
||||
elif msg_num // 100 == 5:
|
||||
logger.trace_if('plugin.edsm.api', 'Event(s) not currently processed, but saved for later')
|
||||
pass
|
||||
else:
|
||||
logger.warning(f'EDSM API call status not 1XX, 2XX or 5XX: {msg.num}')
|
||||
|
||||
for e, r in zip(pending, reply['events']):
|
||||
if not closing and e['event'] in ('StartUp', 'Location', 'FSDJump', 'CarrierJump'):
|
||||
# Update main window's system status
|
||||
this.lastlookup = r
|
||||
# calls update_status in main thread
|
||||
if not config.shutting_down and this.system_link is not None:
|
||||
this.system_link.event_generate('<<EDSMStatus>>', when="tail")
|
||||
if r['msgnum'] // 100 != 1:
|
||||
logger.warning(f'EDSM event with not-1xx status:\n{r["msgnum"]}\n'
|
||||
f'{r["msg"]}\n{json.dumps(e, separators = (",", ": "))}')
|
||||
pending = []
|
||||
break # No exception, so assume success
|
||||
|
||||
except Exception as e:
|
||||
@ -906,7 +957,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 = []
|
||||
@ -980,11 +1031,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:
|
||||
|
267
plugins/inara.py
@ -24,14 +24,13 @@ import json
|
||||
import threading
|
||||
import time
|
||||
import tkinter as tk
|
||||
from collections import OrderedDict, defaultdict, deque
|
||||
from collections import defaultdict, deque
|
||||
from dataclasses import dataclass
|
||||
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 OrderedDict as OrderedDictT
|
||||
from typing import Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union
|
||||
import requests
|
||||
import edmc_data
|
||||
import killswitch
|
||||
@ -43,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
|
||||
@ -102,12 +98,12 @@ class This:
|
||||
self.newsession: bool = True # starting a new session - wait for Cargo event
|
||||
self.undocked: bool = False # just undocked
|
||||
self.suppress_docked = False # Skip initial Docked event if started docked
|
||||
self.cargo: list[OrderedDictT[str, Any]] | None = None
|
||||
self.materials: list[OrderedDictT[str, Any]] | None = None
|
||||
self.cargo: list[dict[str, Any]] | None = None
|
||||
self.materials: list[dict[str, Any]] | None = None
|
||||
self.last_credits: int = 0 # Send credit update soon after Startup / new game
|
||||
self.storedmodules: list[OrderedDictT[str, Any]] | None = None
|
||||
self.loadout: OrderedDictT[str, Any] | None = None
|
||||
self.fleet: list[OrderedDictT[str, Any]] | None = None
|
||||
self.storedmodules: list[dict[str, Any]] | None = None
|
||||
self.loadout: dict[str, Any] | None = None
|
||||
self.fleet: list[dict[str, Any]] | None = None
|
||||
self.shipswap: bool = False # just swapped ship
|
||||
self.on_foot = False
|
||||
|
||||
@ -126,7 +122,7 @@ class This:
|
||||
self.log: 'tk.IntVar'
|
||||
self.log_button: nb.Checkbutton
|
||||
self.label: HyperlinkLabel
|
||||
self.apikey: nb.Entry
|
||||
self.apikey: nb.EntryMenu
|
||||
self.apikey_label: tk.Label
|
||||
|
||||
self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
|
||||
@ -245,7 +241,7 @@ def toggle_password_visibility():
|
||||
this.apikey.config(show="*")
|
||||
|
||||
|
||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame:
|
||||
def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
|
||||
"""Plugin Preferences UI hook."""
|
||||
PADX = 10 # noqa: N806
|
||||
BUTTONX = 12 # noqa: N806 # indent Checkbuttons and Radiobuttons
|
||||
@ -265,7 +261,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.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
|
||||
)
|
||||
@ -281,7 +277,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.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
|
||||
@ -291,9 +287,9 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.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.Entry(frame, show="*", width=50)
|
||||
this.apikey = nb.EntryMenu(frame, show="*", width=50)
|
||||
this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
|
||||
cur_row += 1
|
||||
|
||||
@ -302,7 +298,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.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,
|
||||
)
|
||||
@ -408,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 ''
|
||||
|
||||
@ -433,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 ''
|
||||
|
||||
@ -554,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
|
||||
@ -721,13 +700,13 @@ def journal_entry( # noqa: C901, CCR001
|
||||
this.suppress_docked = True
|
||||
|
||||
# Send cargo and materials if changed
|
||||
cargo = [OrderedDict({'itemName': k, 'itemCount': state['Cargo'][k]}) for k in sorted(state['Cargo'])]
|
||||
cargo = [{'itemName': k, 'itemCount': state['Cargo'][k]} for k in sorted(state['Cargo'])]
|
||||
if this.cargo != cargo:
|
||||
new_add_event('setCommanderInventoryCargo', entry['timestamp'], cargo)
|
||||
this.cargo = cargo
|
||||
|
||||
materials = [
|
||||
OrderedDict([('itemName', k), ('itemCount', state[category][k])])
|
||||
{'itemName': k, 'itemCount': state[category][k]}
|
||||
for category in ('Raw', 'Manufactured', 'Encoded')
|
||||
for k in sorted(state[category])
|
||||
]
|
||||
@ -823,24 +802,30 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
# Fleet
|
||||
if event_name == 'StoredShips':
|
||||
fleet: list[OrderedDictT[str, Any]] = sorted(
|
||||
[OrderedDict({
|
||||
'shipType': x['ShipType'],
|
||||
'shipGameID': x['ShipID'],
|
||||
'shipName': x.get('Name'),
|
||||
'isHot': x['Hot'],
|
||||
'starsystemName': entry['StarSystem'],
|
||||
'stationName': entry['StationName'],
|
||||
'marketID': entry['MarketID'],
|
||||
}) for x in entry['ShipsHere']] +
|
||||
[OrderedDict({
|
||||
'shipType': x['ShipType'],
|
||||
'shipGameID': x['ShipID'],
|
||||
'shipName': x.get('Name'),
|
||||
'isHot': x['Hot'],
|
||||
'starsystemName': x.get('StarSystem'), # Not present for ships in transit
|
||||
'marketID': x.get('ShipMarketID'), # "
|
||||
}) for x in entry['ShipsRemote']],
|
||||
fleet = sorted(
|
||||
[
|
||||
{
|
||||
'shipType': x['ShipType'],
|
||||
'shipGameID': x['ShipID'],
|
||||
'shipName': x.get('Name'),
|
||||
'isHot': x['Hot'],
|
||||
'starsystemName': entry['StarSystem'],
|
||||
'stationName': entry['StationName'],
|
||||
'marketID': entry['MarketID'],
|
||||
}
|
||||
for x in entry['ShipsHere']
|
||||
] +
|
||||
[
|
||||
{
|
||||
'shipType': x['ShipType'],
|
||||
'shipGameID': x['ShipID'],
|
||||
'shipName': x.get('Name'),
|
||||
'isHot': x['Hot'],
|
||||
'starsystemName': x.get('StarSystem'), # Not present for ships in transit
|
||||
'marketID': x.get('ShipMarketID'), # "
|
||||
}
|
||||
for x in entry['ShipsRemote']
|
||||
],
|
||||
key=itemgetter('shipGameID')
|
||||
)
|
||||
|
||||
@ -851,9 +836,8 @@ def journal_entry( # noqa: C901, CCR001
|
||||
# this.events = [x for x in this.events if x['eventName'] != 'setCommanderShip'] # Remove any unsent
|
||||
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
|
||||
@ -867,17 +851,37 @@ 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
|
||||
modules: list[OrderedDictT[str, Any]] = []
|
||||
modules: list[dict[str, Any]] = []
|
||||
for slot in sorted(items):
|
||||
item = items[slot]
|
||||
module: OrderedDictT[str, Any] = OrderedDict([
|
||||
('itemName', item['Name']),
|
||||
('itemValue', item['BuyPrice']),
|
||||
('isHot', item['Hot']),
|
||||
])
|
||||
module: dict[str, Any] = {
|
||||
'itemName': item['Name'],
|
||||
'itemValue': item['BuyPrice'],
|
||||
'isHot': item['Hot'],
|
||||
}
|
||||
|
||||
# Location can be absent if in transit
|
||||
if 'StarSystem' in item:
|
||||
@ -887,7 +891,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
module['marketID'] = item['MarketID']
|
||||
|
||||
if 'EngineerModifications' in item:
|
||||
module['engineering'] = OrderedDict([('blueprintName', item['EngineerModifications'])])
|
||||
module['engineering'] = {'blueprintName': item['EngineerModifications']}
|
||||
if 'Level' in item:
|
||||
module['engineering']['blueprintLevel'] = item['Level']
|
||||
|
||||
@ -907,15 +911,15 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
# Missions
|
||||
if event_name == 'MissionAccepted':
|
||||
data: OrderedDictT[str, Any] = OrderedDict([
|
||||
('missionName', entry['Name']),
|
||||
('missionGameID', entry['MissionID']),
|
||||
('influenceGain', entry['Influence']),
|
||||
('reputationGain', entry['Reputation']),
|
||||
('starsystemNameOrigin', system),
|
||||
('stationNameOrigin', station),
|
||||
('minorfactionNameOrigin', entry['Faction']),
|
||||
])
|
||||
data: dict[str, Any] = {
|
||||
'missionName': entry['Name'],
|
||||
'missionGameID': entry['MissionID'],
|
||||
'influenceGain': entry['Influence'],
|
||||
'reputationGain': entry['Reputation'],
|
||||
'starsystemNameOrigin': system,
|
||||
'stationNameOrigin': station,
|
||||
'minorfactionNameOrigin': entry['Faction'],
|
||||
}
|
||||
|
||||
# optional mission-specific properties
|
||||
for (iprop, prop) in (
|
||||
@ -946,7 +950,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
for x in entry.get('PermitsAwarded', []):
|
||||
new_add_event('addCommanderPermit', entry['timestamp'], {'starsystemName': x})
|
||||
|
||||
data = OrderedDict([('missionGameID', entry['MissionID'])])
|
||||
data = {'missionGameID': entry['MissionID']}
|
||||
if 'Donation' in entry:
|
||||
data['donationCredits'] = entry['Donation']
|
||||
|
||||
@ -966,7 +970,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
factioneffects = []
|
||||
for faction in entry.get('FactionEffects', []):
|
||||
effect: OrderedDictT[str, Any] = OrderedDict([('minorfactionName', faction['Faction'])])
|
||||
effect: dict[str, Any] = {'minorfactionName': faction['Faction']}
|
||||
for influence in faction.get('Influence', []):
|
||||
if 'Influence' in influence:
|
||||
highest_gain = influence['Influence']
|
||||
@ -990,7 +994,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
# Combat
|
||||
if event_name == 'Died':
|
||||
data = OrderedDict([('starsystemName', system)])
|
||||
data = {'starsystemName': system}
|
||||
if 'Killers' in entry:
|
||||
data['wingOpponentNames'] = [x['Name'] for x in entry['Killers']]
|
||||
|
||||
@ -1011,10 +1015,11 @@ def journal_entry( # noqa: C901, CCR001
|
||||
new_add_event('addCommanderCombatDeath', entry['timestamp'], data)
|
||||
|
||||
elif event_name == 'Interdicted':
|
||||
data = OrderedDict([('starsystemName', system),
|
||||
('isPlayer', entry['IsPlayer']),
|
||||
('isSubmit', entry['Submitted']),
|
||||
])
|
||||
data = {
|
||||
'starsystemName': system,
|
||||
'isPlayer': entry['IsPlayer'],
|
||||
'isSubmit': entry['Submitted']
|
||||
}
|
||||
|
||||
if 'Interdictor' in entry:
|
||||
data['opponentName'] = entry['Interdictor']
|
||||
@ -1036,11 +1041,11 @@ def journal_entry( # noqa: C901, CCR001
|
||||
new_add_event('addCommanderCombatInterdicted', entry['timestamp'], data)
|
||||
|
||||
elif event_name == 'Interdiction':
|
||||
data = OrderedDict([
|
||||
('starsystemName', system),
|
||||
('isPlayer', entry['IsPlayer']),
|
||||
('isSuccess', entry['Success']),
|
||||
])
|
||||
data = {
|
||||
'starsystemName': system,
|
||||
'isPlayer': entry['IsPlayer'],
|
||||
'isSuccess': entry['Success'],
|
||||
}
|
||||
|
||||
if 'Interdicted' in entry:
|
||||
data['opponentName'] = entry['Interdicted']
|
||||
@ -1064,10 +1069,10 @@ def journal_entry( # noqa: C901, CCR001
|
||||
new_add_event('addCommanderCombatInterdiction', entry['timestamp'], data)
|
||||
|
||||
elif event_name == 'EscapeInterdiction':
|
||||
data = OrderedDict([
|
||||
('starsystemName', system),
|
||||
('isPlayer', entry['IsPlayer']),
|
||||
])
|
||||
data = {
|
||||
'starsystemName': system,
|
||||
'isPlayer': entry['IsPlayer'],
|
||||
}
|
||||
|
||||
if 'Interdictor' in entry:
|
||||
data['opponentName'] = entry['Interdictor']
|
||||
@ -1281,16 +1286,16 @@ def journal_entry( # noqa: C901, CCR001
|
||||
# ))
|
||||
|
||||
for goal in entry['CurrentGoals']:
|
||||
data = OrderedDict([
|
||||
('communitygoalGameID', goal['CGID']),
|
||||
('communitygoalName', goal['Title']),
|
||||
('starsystemName', goal['SystemName']),
|
||||
('stationName', goal['MarketName']),
|
||||
('goalExpiry', goal['Expiry']),
|
||||
('isCompleted', goal['IsComplete']),
|
||||
('contributorsNum', goal['NumContributors']),
|
||||
('contributionsTotal', goal['CurrentTotal']),
|
||||
])
|
||||
data = {
|
||||
'communitygoalGameID': goal['CGID'],
|
||||
'communitygoalName': goal['Title'],
|
||||
'starsystemName': goal['SystemName'],
|
||||
'stationName': goal['MarketName'],
|
||||
'goalExpiry': goal['Expiry'],
|
||||
'isCompleted': goal['IsComplete'],
|
||||
'contributorsNum': goal['NumContributors'],
|
||||
'contributionsTotal': goal['CurrentTotal'],
|
||||
}
|
||||
|
||||
if 'TierReached' in goal:
|
||||
data['tierReached'] = int(goal['TierReached'].split()[-1])
|
||||
@ -1304,11 +1309,11 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
new_add_event('setCommunityGoal', entry['timestamp'], data)
|
||||
|
||||
data = OrderedDict([
|
||||
('communitygoalGameID', goal['CGID']),
|
||||
('contribution', goal['PlayerContribution']),
|
||||
('percentileBand', goal['PlayerPercentileBand']),
|
||||
])
|
||||
data = {
|
||||
'communitygoalGameID': goal['CGID'],
|
||||
'contribution': goal['PlayerContribution'],
|
||||
'percentileBand': goal['PlayerPercentileBand'],
|
||||
}
|
||||
|
||||
if 'Bonus' in goal:
|
||||
data['percentileBandReward'] = goal['Bonus']
|
||||
@ -1405,7 +1410,7 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001, reanalyze me later
|
||||
pass
|
||||
|
||||
|
||||
def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001
|
||||
def make_loadout(state: dict[str, Any]) -> dict[str, Any]: # noqa: CCR001
|
||||
"""
|
||||
Construct an inara loadout from an event.
|
||||
|
||||
@ -1414,13 +1419,13 @@ def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR0
|
||||
"""
|
||||
modules = []
|
||||
for m in state['Modules'].values():
|
||||
module: OrderedDictT[str, Any] = OrderedDict([
|
||||
('slotName', m['Slot']),
|
||||
('itemName', m['Item']),
|
||||
('itemHealth', m['Health']),
|
||||
('isOn', m['On']),
|
||||
('itemPriority', m['Priority']),
|
||||
])
|
||||
module: dict[str, Any] = {
|
||||
'slotName': m['Slot'],
|
||||
'itemName': m['Item'],
|
||||
'itemHealth': m['Health'],
|
||||
'isOn': m['On'],
|
||||
'itemPriority': m['Priority'],
|
||||
}
|
||||
|
||||
if 'AmmoInClip' in m:
|
||||
module['itemAmmoClip'] = m['AmmoInClip']
|
||||
@ -1435,20 +1440,20 @@ def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR0
|
||||
module['isHot'] = m['Hot']
|
||||
|
||||
if 'Engineering' in m:
|
||||
engineering: OrderedDictT[str, Any] = OrderedDict([
|
||||
('blueprintName', m['Engineering']['BlueprintName']),
|
||||
('blueprintLevel', m['Engineering']['Level']),
|
||||
('blueprintQuality', m['Engineering']['Quality']),
|
||||
])
|
||||
engineering: dict[str, Any] = {
|
||||
'blueprintName': m['Engineering']['BlueprintName'],
|
||||
'blueprintLevel': m['Engineering']['Level'],
|
||||
'blueprintQuality': m['Engineering']['Quality'],
|
||||
}
|
||||
|
||||
if 'ExperimentalEffect' in m['Engineering']:
|
||||
engineering['experimentalEffect'] = m['Engineering']['ExperimentalEffect']
|
||||
|
||||
engineering['modifiers'] = []
|
||||
for mod in m['Engineering']['Modifiers']:
|
||||
modifier: OrderedDictT[str, Any] = OrderedDict([
|
||||
('name', mod['Label']),
|
||||
])
|
||||
modifier: dict[str, Any] = {
|
||||
'name': mod['Label'],
|
||||
}
|
||||
|
||||
if 'OriginalValue' in mod:
|
||||
modifier['value'] = mod['Value']
|
||||
@ -1464,11 +1469,11 @@ def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR0
|
||||
|
||||
modules.append(module)
|
||||
|
||||
return OrderedDict([
|
||||
('shipType', state['ShipType']),
|
||||
('shipGameID', state['ShipID']),
|
||||
('shipLoadout', modules),
|
||||
])
|
||||
return {
|
||||
'shipType': state['ShipType'],
|
||||
'shipGameID': state['ShipID'],
|
||||
'shipLoadout': modules,
|
||||
}
|
||||
|
||||
|
||||
def new_add_event(
|
||||
@ -1637,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:
|
||||
@ -1670,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}'
|
||||
))
|
||||
|
||||
|
438
prefs.py
@ -5,32 +5,28 @@ from __future__ import annotations
|
||||
import contextlib
|
||||
import logging
|
||||
import pathlib
|
||||
import subprocess
|
||||
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 applongname, appversion_nobuild, config
|
||||
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,17 +40,35 @@ 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}"')
|
||||
elif sys.platform.startswith('darwin'):
|
||||
# On macOS, use the "open" command to open the folder
|
||||
system(f'open "{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}"')
|
||||
|
||||
|
||||
def help_open_system_profiler(parent) -> None:
|
||||
"""Open the EDMC System Profiler."""
|
||||
profiler_path = pathlib.Path(config.respath_path)
|
||||
try:
|
||||
if getattr(sys, 'frozen', False):
|
||||
profiler_path /= 'EDMCSystemProfiler.exe'
|
||||
subprocess.run(profiler_path, check=True)
|
||||
else:
|
||||
subprocess.run(['python', "EDMCSystemProfiler.py"], shell=True, check=True)
|
||||
except Exception as err:
|
||||
parent.status["text"] = tr.tl("Error in System Profiler") # LANG: Catch & Record Profiler Errors
|
||||
logger.exception(err)
|
||||
|
||||
|
||||
class PrefsVersion:
|
||||
@ -172,32 +186,7 @@ class AutoInc(contextlib.AbstractContextManager):
|
||||
return None
|
||||
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
import objc # type: ignore
|
||||
from Foundation import NSFileManager # type: ignore
|
||||
try:
|
||||
from ApplicationServices import ( # type: ignore
|
||||
AXIsProcessTrusted, AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt
|
||||
)
|
||||
|
||||
except ImportError:
|
||||
HIServices = objc.loadBundle(
|
||||
'HIServices',
|
||||
globals(),
|
||||
'/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework'
|
||||
)
|
||||
|
||||
objc.loadBundleFunctions(
|
||||
HIServices,
|
||||
globals(),
|
||||
[('AXIsProcessTrusted', 'B'), ('AXIsProcessTrustedWithOptions', 'B@')]
|
||||
)
|
||||
|
||||
objc.loadBundleVariables(HIServices, globals(), [('kAXTrustedCheckOptionPrompt', '@^{__CFString=}')])
|
||||
|
||||
was_accessible_at_launch = AXIsProcessTrusted() # type: ignore
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
import winreg
|
||||
from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
|
||||
@ -247,44 +236,40 @@ class PreferencesDialog(tk.Toplevel):
|
||||
"""The EDMC preferences dialog."""
|
||||
|
||||
def __init__(self, parent: tk.Tk, callback: Optional[Callable]):
|
||||
tk.Toplevel.__init__(self, parent)
|
||||
super().__init__(parent)
|
||||
|
||||
self.parent = parent
|
||||
self.callback = callback
|
||||
if sys.platform == 'darwin':
|
||||
# LANG: File > Preferences menu entry for macOS
|
||||
self.title(_('Preferences'))
|
||||
|
||||
else:
|
||||
# LANG: File > Settings (macOS)
|
||||
self.title(_('Settings'))
|
||||
# LANG: File > Settings (macOS)
|
||||
self.title(tr.tl('Settings'))
|
||||
|
||||
if parent.winfo_viewable():
|
||||
self.transient(parent)
|
||||
|
||||
# position over parent
|
||||
if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
# TODO this is fixed supposedly.
|
||||
self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
|
||||
# Position over parent
|
||||
self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
|
||||
|
||||
# remove decoration
|
||||
# Remove decoration
|
||||
if sys.platform == 'win32':
|
||||
self.attributes('-toolwindow', tk.TRUE)
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
# http://wiki.tcl.tk/13428
|
||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
||||
|
||||
self.resizable(tk.FALSE, tk.FALSE)
|
||||
# Allow the window to be resizable
|
||||
self.resizable(tk.TRUE, tk.TRUE)
|
||||
|
||||
self.cmdr: str | bool | None = False # Note if Cmdr changes in the Journal
|
||||
self.is_beta: bool = False # Note if Beta status changes in the Journal
|
||||
self.cmdrchanged_alarm: Optional[str] = None # This stores an ID that can be used to cancel a scheduled call
|
||||
|
||||
# Set up the main frame
|
||||
frame = ttk.Frame(self)
|
||||
frame.grid(sticky=tk.NSEW)
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.rowconfigure(0, weight=1)
|
||||
frame.columnconfigure(0, weight=1)
|
||||
frame.rowconfigure(0, weight=1)
|
||||
frame.rowconfigure(1, weight=0)
|
||||
|
||||
notebook: ttk.Notebook = nb.Notebook(frame)
|
||||
notebook: nb.Notebook = nb.Notebook(frame)
|
||||
notebook.bind('<<NotebookTabChanged>>', self.tabchanged) # Recompute on tab change
|
||||
|
||||
self.PADX = 10
|
||||
@ -292,29 +277,26 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.LISTX = 25 # indent listed items
|
||||
self.PADY = 1 # close spacing
|
||||
self.BOXY = 2 # box spacing
|
||||
self.SEPY = 10 # seperator line spacing
|
||||
self.SEPY = 10 # separator line spacing
|
||||
|
||||
# Set up different tabs
|
||||
self.__setup_output_tab(notebook)
|
||||
self.__setup_plugin_tabs(notebook)
|
||||
self.__setup_config_tab(notebook)
|
||||
self.__setup_privacy_tab(notebook)
|
||||
self.__setup_appearance_tab(notebook)
|
||||
self.__setup_output_tab(notebook)
|
||||
self.__setup_privacy_tab(notebook)
|
||||
self.__setup_plugin_tab(notebook)
|
||||
self.__setup_plugin_tabs(notebook)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes
|
||||
|
||||
else:
|
||||
buttonframe = ttk.Frame(frame)
|
||||
buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
|
||||
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.grid(row=0, column=1, sticky=tk.E)
|
||||
button.bind("<Return>", lambda event: self.apply())
|
||||
self.protocol("WM_DELETE_WINDOW", self._destroy)
|
||||
# Set up the button frame
|
||||
buttonframe = ttk.Frame(frame)
|
||||
buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
|
||||
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=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)
|
||||
|
||||
# FIXME: Why are these being called when *creating* the Settings window?
|
||||
# Selectively disable buttons depending on output settings
|
||||
@ -326,7 +308,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
# wait for window to appear on screen before calling grab_set
|
||||
self.parent.update_idletasks()
|
||||
self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear ontop of parent on OSX & Linux
|
||||
self.parent.wm_attributes('-topmost', 0) # needed for dialog to appear on top of parent on Linux
|
||||
self.wait_visibility()
|
||||
self.grab_set()
|
||||
|
||||
@ -341,6 +323,15 @@ class PreferencesDialog(tk.Toplevel):
|
||||
):
|
||||
self.geometry(f"+{position.left}+{position.top}")
|
||||
|
||||
# Set Log Directory
|
||||
self.logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
|
||||
|
||||
# Set minimum size to prevent content cut-off
|
||||
self.update_idletasks() # Update "requested size" from geometry manager
|
||||
min_width = self.winfo_reqwidth()
|
||||
min_height = self.winfo_reqheight()
|
||||
self.wm_minsize(min_width, min_height)
|
||||
|
||||
def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None:
|
||||
output_frame = nb.Frame(root_notebook)
|
||||
output_frame.columnconfigure(0, weight=1)
|
||||
@ -354,13 +345,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
|
||||
)
|
||||
@ -369,7 +360,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
|
||||
)
|
||||
@ -379,7 +370,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
|
||||
)
|
||||
@ -389,7 +380,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
|
||||
)
|
||||
@ -398,33 +389,27 @@ 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 = nb.Entry(output_frame, takefocus=False)
|
||||
self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
|
||||
self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
text = (_('Change...')) # LANG: macOS Preferences - files location selection button
|
||||
text = tr.tl('Browse...') # LANG: NOT-macOS Settings - files location selection button
|
||||
|
||||
else:
|
||||
text = (_('Browse...')) # LANG: NOT-macOS Settings - files location selection button
|
||||
|
||||
self.outbutton = nb.Button(
|
||||
self.outbutton = ttk.Button(
|
||||
output_frame,
|
||||
text=text,
|
||||
# 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())
|
||||
|
||||
nb.Frame(output_frame).grid(row=row.get()) # bottom spacer # TODO: does nothing?
|
||||
|
||||
# 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:
|
||||
@ -444,38 +429,34 @@ class PreferencesDialog(tk.Toplevel):
|
||||
logdir = default
|
||||
|
||||
self.logdir.set(logdir)
|
||||
self.logdir_entry = nb.Entry(config_frame, takefocus=False)
|
||||
self.logdir_entry = ttk.Entry(config_frame, takefocus=False)
|
||||
|
||||
# Location of the Journal files
|
||||
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())
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
text = (_('Change...')) # LANG: macOS Preferences - files location selection button
|
||||
|
||||
else:
|
||||
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 = nb.Button(
|
||||
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)
|
||||
|
||||
if config.default_journal_dir_path:
|
||||
# Appearance theme and language setting
|
||||
nb.Button(
|
||||
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)
|
||||
@ -489,17 +470,17 @@ 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 Fleet Carrier CAPI Queries'),
|
||||
variable=self.capi_fleetcarrier
|
||||
).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
if sys.platform in ('darwin', 'win32'):
|
||||
if sys.platform == 'win32':
|
||||
ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
|
||||
columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
@ -511,55 +492,27 @@ class PreferencesDialog(tk.Toplevel):
|
||||
with row as cur_row:
|
||||
nb.Label(
|
||||
config_frame,
|
||||
text=_('Keyboard shortcut') if # LANG: Hotkey/Shortcut settings prompt on OSX
|
||||
sys.platform == 'darwin' else
|
||||
_('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)
|
||||
|
||||
if sys.platform == 'darwin' and not was_accessible_at_launch:
|
||||
if AXIsProcessTrusted():
|
||||
# Shortcut settings prompt on OSX
|
||||
nb.Label(
|
||||
config_frame,
|
||||
# LANG: macOS Preferences > Configuration - restart the app message
|
||||
text=_('Re-start {APP} to use shortcuts').format(APP=applongname),
|
||||
foreground='firebrick'
|
||||
).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER)
|
||||
self.hotkey_text.insert(
|
||||
0,
|
||||
# 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 tr.tl('None')
|
||||
)
|
||||
|
||||
else:
|
||||
# Shortcut settings prompt on OSX
|
||||
nb.Label(
|
||||
config_frame,
|
||||
# LANG: macOS - Configuration - need to grant the app permission for keyboard shortcuts
|
||||
text=_('{APP} needs permission to use shortcuts').format(APP=applongname),
|
||||
foreground='firebrick'
|
||||
).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
||||
# LANG: Shortcut settings button on OSX
|
||||
nb.Button(config_frame, text=_('Open System Preferences'), command=self.enableshortcuts).grid(
|
||||
padx=self.PADX, pady=self.BOXY, sticky=tk.E, row=cur_row
|
||||
)
|
||||
|
||||
else:
|
||||
self.hotkey_text = nb.Entry(config_frame, width=(
|
||||
20 if sys.platform == 'darwin' else 30), justify=tk.CENTER)
|
||||
self.hotkey_text.insert(
|
||||
0,
|
||||
# No hotkey/shortcut currently defined
|
||||
# TODO: display Only shows up on darwin or windows
|
||||
# LANG: No hotkey/shortcut set
|
||||
hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
|
||||
)
|
||||
|
||||
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
|
||||
self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
|
||||
self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||
self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
|
||||
self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
|
||||
self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||
|
||||
# Hotkey/Shortcut setting
|
||||
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
|
||||
)
|
||||
@ -570,22 +523,41 @@ 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
|
||||
)
|
||||
|
||||
self.hotkey_play_btn.grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||
|
||||
# Option to disabled Automatic Check For Updates whilst in-game
|
||||
# Options to select the Update Path and Disable Automatic Checks For Updates whilst in-game
|
||||
ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
|
||||
columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
|
||||
with row as curr_row:
|
||||
nb.Label(config_frame, text=tr.tl('Update Track')).grid( # LANG: Select the Update Track (Beta, Stable)
|
||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=curr_row
|
||||
)
|
||||
self.curr_update_track = "Beta" if config.get_bool('beta_optin') else "Stable"
|
||||
self.update_paths = tk.StringVar(value=self.curr_update_track)
|
||||
|
||||
update_paths = [
|
||||
tr.tl("Stable"), # LANG: Stable Version of EDMC
|
||||
tr.tl("Beta") # LANG: Beta Version of EDMC
|
||||
]
|
||||
self.update_track = nb.OptionMenu(
|
||||
config_frame, self.update_paths, self.update_paths.get(), *update_paths
|
||||
)
|
||||
|
||||
self.update_track.configure(width=15)
|
||||
self.update_track.grid(column=1, pady=self.BOXY, padx=self.PADX, sticky=tk.W, row=curr_row)
|
||||
|
||||
self.disable_autoappupdatecheckingame = tk.IntVar(value=config.get_int('disable_autoappupdatecheckingame'))
|
||||
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
|
||||
)
|
||||
@ -600,7 +572,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()
|
||||
)
|
||||
|
||||
@ -611,7 +583,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')
|
||||
)
|
||||
@ -623,7 +597,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,
|
||||
)
|
||||
@ -637,7 +611,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,
|
||||
@ -655,7 +629,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,
|
||||
@ -676,7 +650,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')
|
||||
@ -700,18 +674,18 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.loglevel_dropdown.configure(width=15)
|
||||
self.loglevel_dropdown.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row)
|
||||
|
||||
nb.Button(
|
||||
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)
|
||||
@ -720,37 +694,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)
|
||||
@ -759,7 +733,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())
|
||||
@ -771,28 +745,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
|
||||
@ -803,10 +778,10 @@ class PreferencesDialog(tk.Toplevel):
|
||||
self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
|
||||
# Main window
|
||||
self.theme_button_0 = nb.ColoredButton(
|
||||
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)
|
||||
)
|
||||
@ -816,7 +791,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
with row as cur_row:
|
||||
self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1])
|
||||
self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
|
||||
self.theme_button_1 = nb.ColoredButton(
|
||||
self.theme_button_1 = tk.Button(
|
||||
appearance_frame,
|
||||
text=' Hutton Orbital ', # Do not translate
|
||||
background='grey4',
|
||||
@ -838,7 +813,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
|
||||
)
|
||||
|
||||
@ -859,7 +834,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
|
||||
@ -869,7 +844,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()
|
||||
@ -889,7 +864,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')
|
||||
@ -911,7 +886,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
|
||||
)
|
||||
@ -923,7 +898,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
|
||||
@ -931,7 +906,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
|
||||
@ -943,11 +918,11 @@ 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()
|
||||
)
|
||||
|
||||
plugdirentry = nb.Entry(plugins_frame, justify=tk.LEFT)
|
||||
plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT)
|
||||
self.displaypath(plugdir, plugdirentry)
|
||||
plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||
|
||||
@ -956,25 +931,26 @@ 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)
|
||||
|
||||
nb.Button(
|
||||
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))
|
||||
if len(enabled_plugins):
|
||||
if enabled_plugins:
|
||||
ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid(
|
||||
columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
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:
|
||||
@ -989,18 +965,18 @@ class PreferencesDialog(tk.Toplevel):
|
||||
############################################################
|
||||
# Show which plugins don't have Python 3.x support
|
||||
############################################################
|
||||
if len(plug.PLUGINS_not_py3):
|
||||
if plug.PLUGINS_not_py3:
|
||||
ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid(
|
||||
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
|
||||
@ -1015,14 +991,14 @@ class PreferencesDialog(tk.Toplevel):
|
||||
# Show disabled plugins
|
||||
############################################################
|
||||
disabled_plugins = list(filter(lambda x: x.folder and not x.module, plug.PLUGINS))
|
||||
if len(disabled_plugins):
|
||||
if disabled_plugins:
|
||||
ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid(
|
||||
columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||
)
|
||||
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:
|
||||
@ -1032,12 +1008,12 @@ class PreferencesDialog(tk.Toplevel):
|
||||
############################################################
|
||||
# Show plugins that failed to load
|
||||
############################################################
|
||||
if len(plug.PLUGINS_broken):
|
||||
if plug.PLUGINS_broken:
|
||||
ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid(
|
||||
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()
|
||||
)
|
||||
|
||||
@ -1048,7 +1024,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):
|
||||
"""
|
||||
@ -1070,14 +1046,6 @@ class PreferencesDialog(tk.Toplevel):
|
||||
def tabchanged(self, event: tk.Event) -> None:
|
||||
"""Handle preferences active tab changing."""
|
||||
self.outvarchanged()
|
||||
if sys.platform == 'darwin':
|
||||
# Hack to recompute size so that buttons show up under Mojave
|
||||
notebook = event.widget
|
||||
frame = self.nametowidget(notebook.winfo_parent())
|
||||
temp = nb.Label(frame)
|
||||
temp.grid()
|
||||
temp.update_idletasks()
|
||||
temp.destroy()
|
||||
|
||||
def outvarchanged(self, event: Optional[tk.Event] = None) -> None:
|
||||
"""Handle Output tab variable changes."""
|
||||
@ -1139,16 +1107,6 @@ class PreferencesDialog(tk.Toplevel):
|
||||
entryfield.insert(0, '\\'.join(display))
|
||||
|
||||
# None if path doesn't exist
|
||||
elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()):
|
||||
if pathvar.get().startswith(config.home):
|
||||
display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[
|
||||
len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)):
|
||||
]
|
||||
|
||||
else:
|
||||
display = NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())
|
||||
|
||||
entryfield.insert(0, '/'.join(display))
|
||||
else:
|
||||
if pathvar.get().startswith(config.home):
|
||||
entryfield.insert(0, '~' + pathvar.get()[len(config.home):])
|
||||
@ -1219,7 +1177,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:
|
||||
"""
|
||||
@ -1252,7 +1210,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
|
||||
|
||||
@ -1288,12 +1246,12 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
config.set('capi_fleetcarrier', self.capi_fleetcarrier.get())
|
||||
|
||||
if sys.platform in ('darwin', 'win32'):
|
||||
if sys.platform == 'win32':
|
||||
config.set('hotkey_code', self.hotkey_code)
|
||||
config.set('hotkey_mods', self.hotkey_mods)
|
||||
config.set('hotkey_always', int(not self.hotkey_only.get()))
|
||||
config.set('hotkey_mute', int(not self.hotkey_play.get()))
|
||||
|
||||
config.set('beta_optin', 0 if self.update_paths.get() == "Stable" else 1)
|
||||
config.set('shipyard_provider', self.shipyard_provider.get())
|
||||
config.set('system_provider', self.system_provider.get())
|
||||
config.set('station_provider', self.station_provider.get())
|
||||
@ -1302,7 +1260,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())
|
||||
@ -1317,9 +1275,15 @@ class PreferencesDialog(tk.Toplevel):
|
||||
config.set('dark_highlight', self.theme_colors[1])
|
||||
theme.apply(self.parent)
|
||||
|
||||
# Send to the Post Config if we updated the update branch
|
||||
post_flags = {
|
||||
'Update': True if self.curr_update_track != self.update_paths.get() else False,
|
||||
'Track': self.update_paths.get(),
|
||||
'Parent': self
|
||||
}
|
||||
# Notify
|
||||
if self.callback:
|
||||
self.callback()
|
||||
self.callback(**post_flags)
|
||||
|
||||
plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta)
|
||||
|
||||
@ -1333,25 +1297,3 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0)
|
||||
self.destroy()
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
def enableshortcuts(self) -> None:
|
||||
"""Set up macOS preferences shortcut."""
|
||||
self.apply()
|
||||
# popup System Preferences dialog
|
||||
try:
|
||||
# http://stackoverflow.com/questions/6652598/cocoa-button-opens-a-system-preference-page/6658201
|
||||
from ScriptingBridge import SBApplication # type: ignore
|
||||
sysprefs = 'com.apple.systempreferences'
|
||||
prefs = SBApplication.applicationWithBundleIdentifier_(sysprefs)
|
||||
pane = [x for x in prefs.panes() if x.id() == 'com.apple.preference.security'][0]
|
||||
prefs.setCurrentPane_(pane)
|
||||
anchor = [x for x in pane.anchors() if x.name() == 'Privacy_Accessibility'][0]
|
||||
anchor.reveal()
|
||||
prefs.activate()
|
||||
|
||||
except Exception:
|
||||
AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True})
|
||||
|
||||
if not config.shutting_down:
|
||||
self.parent.event_generate('<<Quit>>', when="tail")
|
||||
|
85
protocol.py
@ -26,6 +26,7 @@ is_wine = False
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from ctypes import windll # type: ignore
|
||||
|
||||
try:
|
||||
if windll.ntdll.wine_get_version:
|
||||
is_wine = True
|
||||
@ -58,72 +59,13 @@ class GenericProtocolHandler:
|
||||
self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
|
||||
|
||||
|
||||
if sys.platform == 'darwin' and getattr(sys, 'frozen', False): # noqa: C901 # its guarding ALL macos stuff.
|
||||
import struct
|
||||
|
||||
import objc # type: ignore
|
||||
from AppKit import NSAppleEventManager, NSObject # type: ignore
|
||||
|
||||
kInternetEventClass = kAEGetURL = struct.unpack('>l', b'GURL')[0] # noqa: N816 # API names
|
||||
keyDirectObject = struct.unpack('>l', b'----')[0] # noqa: N816 # API names
|
||||
|
||||
class DarwinProtocolHandler(GenericProtocolHandler):
|
||||
"""
|
||||
MacOS protocol handler implementation.
|
||||
|
||||
Uses macOS event stuff.
|
||||
"""
|
||||
|
||||
POLL = 100 # ms
|
||||
|
||||
def start(self, master: 'tkinter.Tk') -> None:
|
||||
"""Start Protocol Handler."""
|
||||
GenericProtocolHandler.start(self, master)
|
||||
self.lasturl: str | None = None
|
||||
self.eventhandler = EventHandler.alloc().init()
|
||||
|
||||
def poll(self) -> None:
|
||||
"""Poll event until URL is updated."""
|
||||
# No way of signalling to Tkinter from within the callback handler block that doesn't cause Python to crash,
|
||||
# so poll. TODO: Resolved?
|
||||
if self.lasturl and self.lasturl.startswith(self.redirect):
|
||||
self.event(self.lasturl)
|
||||
self.lasturl = None
|
||||
|
||||
class EventHandler(NSObject):
|
||||
"""Handle NSAppleEventManager IPC stuff."""
|
||||
|
||||
def init(self) -> None:
|
||||
"""
|
||||
Init method for handler.
|
||||
|
||||
(I'd assume this is related to the subclassing of NSObject for why its not __init__)
|
||||
"""
|
||||
self = objc.super(EventHandler, self).init()
|
||||
NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID_(
|
||||
self,
|
||||
'handleEvent:withReplyEvent:',
|
||||
kInternetEventClass,
|
||||
kAEGetURL
|
||||
)
|
||||
return self
|
||||
|
||||
def handleEvent_withReplyEvent_(self, event, replyEvent) -> None: # noqa: N802 N803 # Required to override
|
||||
"""Actual event handling from NSAppleEventManager."""
|
||||
protocolhandler.lasturl = parse.unquote(
|
||||
event.paramDescriptorForKeyword_(keyDirectObject).stringValue()
|
||||
).strip()
|
||||
|
||||
protocolhandler.master.after(DarwinProtocolHandler.POLL, protocolhandler.poll)
|
||||
|
||||
|
||||
elif (config.auth_force_edmc_protocol
|
||||
or (
|
||||
sys.platform == 'win32'
|
||||
and getattr(sys, 'frozen', False)
|
||||
and not is_wine
|
||||
and not config.auth_force_localserver
|
||||
)):
|
||||
if (config.auth_force_edmc_protocol # noqa: C901
|
||||
or (
|
||||
sys.platform == 'win32'
|
||||
and getattr(sys, 'frozen', False)
|
||||
and not is_wine
|
||||
and not config.auth_force_localserver
|
||||
)):
|
||||
# This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
|
||||
assert sys.platform == 'win32'
|
||||
# spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
|
||||
@ -245,11 +187,11 @@ elif (config.auth_force_edmc_protocol
|
||||
# which we can read out as shown below, and then compare.
|
||||
|
||||
target_is_valid = lparam_low == 0 or (
|
||||
GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
|
||||
GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
|
||||
)
|
||||
|
||||
topic_is_valid = lparam_high == 0 or (
|
||||
GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
|
||||
GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
|
||||
)
|
||||
|
||||
if target_is_valid and topic_is_valid:
|
||||
@ -480,12 +422,9 @@ def get_handler_impl() -> Type[GenericProtocolHandler]:
|
||||
|
||||
:return: An instantiatable GenericProtocolHandler
|
||||
"""
|
||||
if sys.platform == 'darwin' and getattr(sys, 'frozen', False):
|
||||
return DarwinProtocolHandler # pyright: reportUnboundVariable=false
|
||||
|
||||
if (
|
||||
(sys.platform == 'win32' and config.auth_force_edmc_protocol)
|
||||
or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
|
||||
(sys.platform == 'win32' and config.auth_force_edmc_protocol)
|
||||
or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
|
||||
):
|
||||
return WindowsProtocolHandler
|
||||
|
||||
|
@ -23,6 +23,3 @@ sys-platform-not-darwin = "sys_platform == 'darwin'"
|
||||
sys-platform-linux = "sys_platform != 'linux'"
|
||||
sys-platform-not-linux = "sys_platform == 'linux'"
|
||||
sys-platform-not-known = "sys_platform in ('darwin', 'linux', 'win32')"
|
||||
|
||||
[tool.pyright]
|
||||
# pythonPlatform = 'Darwin'
|
||||
|
@ -5,7 +5,7 @@ wheel
|
||||
# We can't rely on just picking this up from either the base (not venv),
|
||||
# or venv-init-time version. Specify here so that dependabot will prod us
|
||||
# about new versions.
|
||||
setuptools==69.1.1
|
||||
setuptools==70.0.0
|
||||
|
||||
# Static analysis tools
|
||||
flake8==7.0.0
|
||||
@ -13,22 +13,22 @@ 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.8.0
|
||||
mypy==1.10.0
|
||||
pep8-naming==0.13.3
|
||||
safety==2.3.5
|
||||
types-requests==2.31.0.20240125
|
||||
safety==3.2.0
|
||||
types-requests==2.31.0.20240311
|
||||
types-pkg-resources==0.1.3
|
||||
|
||||
# Code formatting tools
|
||||
autopep8==2.0.4
|
||||
autopep8==2.2.0
|
||||
|
||||
# Git pre-commit checking
|
||||
pre-commit==3.6.2
|
||||
pre-commit==3.7.1
|
||||
|
||||
# HTML changelogs
|
||||
grip==4.6.2
|
||||
@ -38,9 +38,9 @@ grip==4.6.2
|
||||
py2exe==0.13.0.1; sys_platform == 'win32'
|
||||
|
||||
# Testing
|
||||
pytest==8.0.2
|
||||
pytest-cov==4.1.0 # Pytest code coverage support
|
||||
coverage[toml]==7.4.1 # 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,14 +1,5 @@
|
||||
certifi==2024.2.2
|
||||
requests==2.31.0
|
||||
# requests depends on this now ?
|
||||
charset-normalizer==3.3.2
|
||||
|
||||
watchdog==3.0.0
|
||||
# Commented out because this doesn't package well with py2exe
|
||||
requests==2.32.3
|
||||
pillow==10.3.0
|
||||
watchdog==4.0.0
|
||||
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
|
||||
|
||||
# Base requirement for MacOS
|
||||
pyobjc; sys_platform == 'darwin'
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
248
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'] = ''
|
||||
@ -374,7 +371,7 @@ class StatsResults(tk.Toplevel):
|
||||
self.transient(parent)
|
||||
|
||||
# position over parent
|
||||
if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
if parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
|
||||
self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}")
|
||||
|
||||
# remove decoration
|
||||
@ -382,10 +379,6 @@ class StatsResults(tk.Toplevel):
|
||||
if sys.platform == 'win32':
|
||||
self.attributes('-toolwindow', tk.TRUE)
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
# http://wiki.tcl.tk/13428
|
||||
parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
|
||||
|
||||
frame = ttk.Frame(self)
|
||||
frame.grid(sticky=tk.NSEW)
|
||||
|
||||
@ -405,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)
|
||||
@ -421,14 +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
|
||||
|
||||
if sys.platform != 'darwin':
|
||||
buttonframe = ttk.Frame(frame)
|
||||
buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW) # type: ignore # the tuple is supported
|
||||
buttonframe.columnconfigure(0, weight=1)
|
||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
||||
ttk.Button(buttonframe, text='OK', command=self.destroy).grid(row=0, column=1, sticky=tk.E)
|
||||
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()
|
||||
|
3
td.py
@ -1,7 +1,6 @@
|
||||
"""Export data for Trade Dangerous."""
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from operator import itemgetter
|
||||
@ -32,7 +31,7 @@ def export(data: CAPIData) -> None:
|
||||
with open(data_path / data_filename, 'wb') as h:
|
||||
# Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
|
||||
h.write('#! trade.py import -\n'.encode('utf-8'))
|
||||
this_platform = "Mac OS" if sys.platform == 'darwin' else system()
|
||||
this_platform = system()
|
||||
cmdr_name = data['commander']['name'].strip()
|
||||
h.write(
|
||||
f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')
|
||||
|
@ -5,21 +5,15 @@ import numbers
|
||||
import sys
|
||||
import warnings
|
||||
from configparser import NoOptionError
|
||||
from os import getenv, makedirs, mkdir, pardir
|
||||
from os.path import dirname, expanduser, isdir, join, normpath
|
||||
from os import getenv, makedirs, mkdir
|
||||
from os.path import dirname, expanduser, isdir, join
|
||||
from typing import TYPE_CHECKING
|
||||
from config import applongname, appname, update_interval
|
||||
from EDMCLogging import get_main_logger
|
||||
|
||||
logger = get_main_logger()
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
from Foundation import ( # type: ignore
|
||||
NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains,
|
||||
NSUserDefaults, NSUserDomainMask
|
||||
)
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
import uuid
|
||||
from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR
|
||||
@ -115,91 +109,7 @@ class OldConfig:
|
||||
OUT_EDDN_DELAY = 4096
|
||||
OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
|
||||
|
||||
if sys.platform == 'darwin': # noqa: C901 # It's gating *all* the functions
|
||||
|
||||
def __init__(self):
|
||||
self.app_dir = join(
|
||||
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], appname
|
||||
)
|
||||
if not isdir(self.app_dir):
|
||||
mkdir(self.app_dir)
|
||||
|
||||
self.plugin_dir = join(self.app_dir, 'plugins')
|
||||
if not isdir(self.plugin_dir):
|
||||
mkdir(self.plugin_dir)
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
self.internal_plugin_dir = normpath(join(dirname(sys.executable), pardir, 'Library', 'plugins'))
|
||||
self.respath = normpath(join(dirname(sys.executable), pardir, 'Resources'))
|
||||
self.identifier = NSBundle.mainBundle().bundleIdentifier()
|
||||
|
||||
else:
|
||||
self.internal_plugin_dir = join(dirname(__file__), 'plugins')
|
||||
self.respath = dirname(__file__)
|
||||
# Don't use Python's settings if interactive
|
||||
self.identifier = f'uk.org.marginal.{appname.lower()}'
|
||||
NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
|
||||
|
||||
self.default_journal_dir: str | None = join(
|
||||
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0],
|
||||
'Frontier Developments',
|
||||
'Elite Dangerous'
|
||||
)
|
||||
self.home = expanduser('~')
|
||||
|
||||
self.defaults = NSUserDefaults.standardUserDefaults()
|
||||
self.settings = dict(self.defaults.persistentDomainForName_(self.identifier) or {}) # make writeable
|
||||
|
||||
# Check out_dir exists
|
||||
if not self.get('outdir') or not isdir(str(self.get('outdir'))):
|
||||
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
|
||||
|
||||
def get(self, key: str, default: None | list | str = None) -> None | list | str:
|
||||
"""Look up a string configuration value."""
|
||||
val = self.settings.get(key)
|
||||
if val is None:
|
||||
return default
|
||||
|
||||
if isinstance(val, str):
|
||||
return str(val)
|
||||
|
||||
if isinstance(val, list):
|
||||
return list(val) # make writeable
|
||||
|
||||
return default
|
||||
|
||||
def getint(self, key: str, default: int = 0) -> int:
|
||||
"""Look up an integer configuration value."""
|
||||
try:
|
||||
return int(self.settings.get(key, default)) # should already be int, but check by casting
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Failed to int({key=})", exc_info=e)
|
||||
return default
|
||||
|
||||
except Exception as e:
|
||||
logger.debug('The exception type is ...', exc_info=e)
|
||||
return default
|
||||
|
||||
def set(self, key: str, val: int | str | list) -> None:
|
||||
"""Set value on the specified configuration key."""
|
||||
self.settings[key] = val
|
||||
|
||||
def delete(self, key: str) -> None:
|
||||
"""Delete the specified configuration key."""
|
||||
self.settings.pop(key, None)
|
||||
|
||||
def save(self) -> None:
|
||||
"""Save current configuration to disk."""
|
||||
self.defaults.setPersistentDomain_forName_(self.settings, self.identifier)
|
||||
self.defaults.synchronize()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the configuration."""
|
||||
self.save()
|
||||
self.defaults = None
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32': # noqa: C901
|
||||
|
||||
def __init__(self):
|
||||
self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore
|
||||
|
32
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
|
||||
|
||||
@ -150,7 +147,7 @@ class _Theme:
|
||||
# the widget has explicit fg or bg attributes.
|
||||
assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
|
||||
if not self.defaults:
|
||||
# Can't initialise this til window is created # Windows, MacOS
|
||||
# Can't initialise this til window is created # Windows
|
||||
self.defaults = {
|
||||
'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText
|
||||
'bg': tk.Label()['background'], # SystemButtonFace, White
|
||||
@ -268,8 +265,7 @@ class _Theme:
|
||||
# (Mostly) system colors
|
||||
style = ttk.Style()
|
||||
self.current = {
|
||||
'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or
|
||||
style.lookup('TLabel', 'background')),
|
||||
'background': (style.lookup('TLabel', 'background')),
|
||||
'foreground': style.lookup('TLabel', 'foreground'),
|
||||
'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or
|
||||
style.lookup('TLabel', 'background', ['active'])),
|
||||
@ -292,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'),
|
||||
}
|
||||
@ -366,8 +362,6 @@ class _Theme:
|
||||
if 'bg' not in attribs:
|
||||
widget['background'] = self.current['background']
|
||||
widget['activebackground'] = self.current['activebackground']
|
||||
if sys.platform == 'darwin' and isinstance(widget, tk.Button):
|
||||
widget['highlightbackground'] = self.current['background']
|
||||
|
||||
if 'font' not in attribs:
|
||||
widget['font'] = self.current['font']
|
||||
@ -426,21 +420,7 @@ class _Theme:
|
||||
return # Don't need to mess with the window manager
|
||||
self.active = theme
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask
|
||||
root.update_idletasks() # need main window to be created
|
||||
if theme == self.THEME_DEFAULT:
|
||||
appearance = NSAppearance.appearanceNamed_('NSAppearanceNameAqua')
|
||||
|
||||
else: # Dark (Transparent only on win32)
|
||||
appearance = NSAppearance.appearanceNamed_('NSAppearanceNameDarkAqua')
|
||||
|
||||
for window in NSApplication.sharedApplication().windows():
|
||||
window.setStyleMask_(window.styleMask() & ~(
|
||||
NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom
|
||||
window.setAppearance_(appearance)
|
||||
|
||||
elif sys.platform == 'win32':
|
||||
if sys.platform == 'win32':
|
||||
GWL_STYLE = -16 # noqa: N806 # ctypes
|
||||
WS_MAXIMIZEBOX = 0x00010000 # noqa: N806 # ctypes
|
||||
# tk8.5.9/win/tkWinWm.c:342
|
||||
|
@ -19,21 +19,37 @@ In addition to standard ttk.Label arguments, takes the following arguments:
|
||||
May be imported by plugins
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
from functools import partial
|
||||
import sys
|
||||
import tkinter as tk
|
||||
import warnings
|
||||
import webbrowser
|
||||
from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
import plug
|
||||
from os import path
|
||||
from config import config, logger
|
||||
from l10n import translations as tr
|
||||
from monitor import monitor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
def _(x: str) -> str: return x
|
||||
SHIPYARD_HTML_TEMPLATE = """
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url={link}">
|
||||
<title>Redirecting you to your {ship_name} at {provider_name}...</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="{link}">
|
||||
You should be redirected to your {ship_name} at {provider_name} shortly...
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
# FIXME: Split this into multi-file module to separate the platforms
|
||||
class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # type: ignore
|
||||
class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
"""Clickable label for HTTP links."""
|
||||
|
||||
def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None:
|
||||
@ -51,22 +67,10 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
||||
self.foreground = kw.get('foreground', 'blue')
|
||||
self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup(
|
||||
'TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
# Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult
|
||||
kw['background'] = kw.pop('background', 'systemDialogBackgroundActive')
|
||||
kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label
|
||||
tk.Label.__init__(self, master, **kw)
|
||||
|
||||
else:
|
||||
ttk.Label.__init__(self, master, **kw)
|
||||
ttk.Label.__init__(self, master, **kw)
|
||||
|
||||
self.bind('<Button-1>', self._click)
|
||||
|
||||
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.bind(sys.platform == 'darwin' and '<Button-2>' or '<Button-3>', self._contextmenu)
|
||||
self.bind('<Button-3>', self._contextmenu)
|
||||
|
||||
self.bind('<Enter>', self._enter)
|
||||
self.bind('<Leave>', self._leave)
|
||||
@ -76,6 +80,48 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
||||
text=kw.get('text'),
|
||||
font=kw.get('font', ttk.Style().lookup('TLabel', 'font')))
|
||||
|
||||
# Add Menu Options
|
||||
self.plug_options = kw.pop('plug_options', None)
|
||||
self.name = kw.get('name', None)
|
||||
|
||||
def open_shipyard(self, url: str):
|
||||
"""Open the Current Ship Loadout in the Selected Provider."""
|
||||
if not (loadout := monitor.ship()):
|
||||
logger.warning('No ship loadout, aborting.')
|
||||
return ''
|
||||
if not bool(config.get_int("use_alt_shipyard_open")):
|
||||
opener = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta)
|
||||
if opener:
|
||||
return webbrowser.open(opener)
|
||||
else:
|
||||
# Avoid file length limits if possible
|
||||
target = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta)
|
||||
file_name = path.join(config.app_dir_path, "last_shipyard.html")
|
||||
|
||||
with open(file_name, 'w') as f:
|
||||
f.write(SHIPYARD_HTML_TEMPLATE.format(
|
||||
link=html.escape(str(target)),
|
||||
provider_name=html.escape(str(url)),
|
||||
ship_name=html.escape("Ship")
|
||||
))
|
||||
|
||||
webbrowser.open(f'file://localhost/{file_name}')
|
||||
|
||||
def open_system(self, url: str):
|
||||
"""Open the Current System in the Selected Provider."""
|
||||
opener = plug.invoke(url, 'EDSM', 'system_url', monitor.state['SystemName'])
|
||||
if opener:
|
||||
return webbrowser.open(opener)
|
||||
|
||||
def open_station(self, url: str):
|
||||
"""Open the Current Station in the Selected Provider."""
|
||||
opener = plug.invoke(
|
||||
url, 'EDSM', 'station_url',
|
||||
monitor.state['SystemName'], monitor.state['StationName']
|
||||
)
|
||||
if opener:
|
||||
return webbrowser.open(opener)
|
||||
|
||||
def configure( # noqa: CCR001
|
||||
self, cnf: dict[str, Any] | None = None, **kw: Any
|
||||
) -> dict[str, tuple[str, str, str, Any, Any]] | None:
|
||||
@ -107,10 +153,9 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
||||
if state == tk.DISABLED:
|
||||
kw['cursor'] = 'arrow' # System default
|
||||
elif self.url and (kw['text'] if 'text' in kw else self['text']):
|
||||
kw['cursor'] = 'pointinghand' if sys.platform == 'darwin' else 'hand2'
|
||||
kw['cursor'] = 'hand2'
|
||||
else:
|
||||
kw['cursor'] = 'notallowed' if sys.platform == 'darwin' else (
|
||||
'no' if sys.platform == 'win32' else 'circle')
|
||||
kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle')
|
||||
|
||||
return super().configure(cnf, **kw)
|
||||
|
||||
@ -139,51 +184,43 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
|
||||
webbrowser.open(url)
|
||||
|
||||
def _contextmenu(self, event: tk.Event) -> None:
|
||||
"""
|
||||
Display the context menu when right-clicked.
|
||||
|
||||
:param event: The event object.
|
||||
"""
|
||||
menu = tk.Menu(tearoff=tk.FALSE)
|
||||
# LANG: Label for 'Copy' as in 'Copy and Paste'
|
||||
menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste
|
||||
|
||||
if self.name == 'ship':
|
||||
menu.add_separator()
|
||||
for url in plug.provides('shipyard_url'):
|
||||
menu.add_command(
|
||||
label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider
|
||||
command=partial(self.open_shipyard, url)
|
||||
)
|
||||
|
||||
if self.name == 'station':
|
||||
menu.add_separator()
|
||||
for url in plug.provides('station_url'):
|
||||
menu.add_command(
|
||||
label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider
|
||||
command=partial(self.open_station, url)
|
||||
)
|
||||
|
||||
if self.name == 'system':
|
||||
menu.add_separator()
|
||||
for url in plug.provides('system_url'):
|
||||
menu.add_command(
|
||||
label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider
|
||||
command=partial(self.open_system, url)
|
||||
)
|
||||
|
||||
if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
|
||||
self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root)
|
||||
menu.post(event.x_root, event.y_root)
|
||||
|
||||
def copy(self) -> None:
|
||||
"""Copy the current text to the clipboard."""
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(self['text'])
|
||||
|
||||
|
||||
def openurl(url: str) -> None:
|
||||
r"""
|
||||
Open the given URL in appropriate browser.
|
||||
|
||||
2022-12-06:
|
||||
Firefox itself will gladly attempt to use very long URLs in its URL
|
||||
input. Up to 16384 was attempted, but the Apache instance this was
|
||||
tested against only allowed up to 8207 total URL length to pass, that
|
||||
being 8190 octets of REQUEST_URI (path + GET params).
|
||||
|
||||
Testing from Windows 10 Home 21H2 cmd.exe with:
|
||||
|
||||
"<path to>\firefox.exe" -osint -url "<test url>"
|
||||
|
||||
only allowed 8115 octest of REQUEST_URI to pass through.
|
||||
|
||||
Microsoft Edge yielded 8092 octets. Google Chrome yielded 8093 octets.
|
||||
|
||||
However, this is actually the limit of how long a CMD.EXE command-line
|
||||
can be. The URL was being cut off *there*.
|
||||
|
||||
The 8207 octet URL makes it through `webbrowser.open(<url>)` to:
|
||||
|
||||
Firefox 107.0.1
|
||||
Microsoft Edge 108.0.1462.42
|
||||
Google Chrome 108.0.5359.95
|
||||
|
||||
This was also tested as working *with* the old winreg/subprocess code,
|
||||
so it wasn't even suffering from the same limit as CMD.EXE.
|
||||
|
||||
Conclusion: No reason to not just use `webbrowser.open()`, as prior
|
||||
to e280d6c2833c25867b8139490e68ddf056477917 there was a bug, introduced
|
||||
in 5989acd0d3263e54429ff99769ff73a20476d863, which meant the code always
|
||||
ended up using `webbrowser.open()` *anyway*.
|
||||
:param url: URL to open.
|
||||
"""
|
||||
warnings.warn("This function is deprecated. "
|
||||
"Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2)
|
||||
webbrowser.open(url)
|
||||
|
76
update.py
@ -7,17 +7,17 @@ See LICENSE file.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import threading
|
||||
from os.path import dirname, join
|
||||
from traceback import print_exc
|
||||
from typing import TYPE_CHECKING
|
||||
from xml.etree import ElementTree
|
||||
import requests
|
||||
import semantic_version
|
||||
from config import appname, appversion_nobuild, config, update_feed
|
||||
from config import appname, appversion_nobuild, config, get_update_feed
|
||||
from EDMCLogging import get_main_logger
|
||||
from l10n import translations as tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import tkinter as tk
|
||||
@ -26,6 +26,38 @@ if TYPE_CHECKING:
|
||||
logger = get_main_logger()
|
||||
|
||||
|
||||
def check_for_fdev_updates(silent: bool = False) -> None: # noqa: CCR001
|
||||
"""Check for and download FDEV ID file updates."""
|
||||
files_urls = [
|
||||
('commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/commodity.csv'),
|
||||
('rare_commodity.csv', 'https://raw.githubusercontent.com/EDCD/FDevIDs/master/rare_commodity.csv')
|
||||
]
|
||||
|
||||
for file, url in files_urls:
|
||||
fdevid_file = pathlib.Path(config.respath_path / 'FDevIDs' / file)
|
||||
fdevid_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
with open(fdevid_file, newline='', encoding='utf-8') as f:
|
||||
local_content = f.read()
|
||||
except FileNotFoundError:
|
||||
local_content = None
|
||||
|
||||
response = requests.get(url)
|
||||
if response.status_code != 200:
|
||||
if not silent:
|
||||
logger.error(f'Failed to download {file}! Unable to continue.')
|
||||
continue
|
||||
|
||||
if local_content == response.text:
|
||||
if not silent:
|
||||
logger.info(f'FDEV ID file {file} already up to date.')
|
||||
else:
|
||||
if not silent:
|
||||
logger.info(f'FDEV ID file {file} not up to date. Downloading...')
|
||||
with open(fdevid_file, 'w', newline='', encoding='utf-8') as csvfile:
|
||||
csvfile.write(response.text)
|
||||
|
||||
|
||||
class EDMCVersion:
|
||||
"""
|
||||
Hold all the information about an EDMC version.
|
||||
@ -91,7 +123,7 @@ class Updater:
|
||||
self.updater: ctypes.CDLL | None = ctypes.cdll.WinSparkle
|
||||
|
||||
# Set the appcast URL
|
||||
self.updater.win_sparkle_set_appcast_url(update_feed.encode())
|
||||
self.updater.win_sparkle_set_appcast_url(get_update_feed().encode())
|
||||
|
||||
# Set the appversion *without* build metadata, as WinSparkle
|
||||
# doesn't do proper Semantic Version checks.
|
||||
@ -114,21 +146,6 @@ class Updater:
|
||||
|
||||
return
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
import objc
|
||||
|
||||
try:
|
||||
objc.loadBundle(
|
||||
'Sparkle', globals(), join(dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework')
|
||||
)
|
||||
# loadBundle presumably supplies `SUUpdater`
|
||||
self.updater = SUUpdater.sharedUpdater() # noqa: F821
|
||||
|
||||
except Exception:
|
||||
# can't load framework - not frozen or not included in app bundle?
|
||||
print_exc()
|
||||
self.updater = None
|
||||
|
||||
def set_automatic_updates_check(self, onoroff: bool) -> None:
|
||||
"""
|
||||
Set (Win)Sparkle to perform automatic update checks, or not.
|
||||
@ -141,9 +158,6 @@ class Updater:
|
||||
if sys.platform == 'win32' and self.updater:
|
||||
self.updater.win_sparkle_set_automatic_check_for_updates(onoroff)
|
||||
|
||||
if sys.platform == 'darwin' and self.updater:
|
||||
self.updater.SUEnableAutomaticChecks(onoroff)
|
||||
|
||||
def check_for_updates(self) -> None:
|
||||
"""Trigger the requisite method to check for an update."""
|
||||
if self.use_internal():
|
||||
@ -154,8 +168,7 @@ class Updater:
|
||||
elif sys.platform == 'win32' and self.updater:
|
||||
self.updater.win_sparkle_check_update_with_ui()
|
||||
|
||||
elif sys.platform == 'darwin' and self.updater:
|
||||
self.updater.checkForUpdates_(None)
|
||||
check_for_fdev_updates()
|
||||
|
||||
def check_appcast(self) -> EDMCVersion | None:
|
||||
"""
|
||||
@ -168,7 +181,7 @@ class Updater:
|
||||
newversion = None
|
||||
items = {}
|
||||
try:
|
||||
request = requests.get(update_feed, timeout=10)
|
||||
request = requests.get(get_update_feed(), timeout=10)
|
||||
|
||||
except requests.RequestException as ex:
|
||||
logger.exception(f'Error retrieving update_feed file: {ex}')
|
||||
@ -183,13 +196,9 @@ class Updater:
|
||||
|
||||
return None
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
sparkle_platform = 'macos'
|
||||
|
||||
else:
|
||||
# For *these* purposes anything else is the same as 'windows', as
|
||||
# non-win32 would be running from source.
|
||||
sparkle_platform = 'windows'
|
||||
# For *these* purposes all systems are the same as 'windows', as
|
||||
# non-win32 would be running from source.
|
||||
sparkle_platform = 'windows'
|
||||
|
||||
for item in feed.findall('channel/item'):
|
||||
# xml is a pain with types, hence these ignores
|
||||
@ -225,7 +234,8 @@ class Updater:
|
||||
|
||||
if newversion and self.root:
|
||||
status = self.root.nametowidget(f'.{appname.lower()}.status')
|
||||
status['text'] = newversion.title + ' is available'
|
||||
# LANG: Update Available Text
|
||||
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", "<", ">", ":", '"', "/", "\\", "|", "?", "*")}
|
||||
)
|
||||
|