1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-13 07:47:14 +03:00

Merge branch 'main' into stable

This commit is contained in:
David Sangrey 2024-06-04 17:01:50 -04:00
commit d0d227d7ec
No known key found for this signature in database
GPG Key ID: 6A95067A0EF46336
83 changed files with 2660 additions and 2927 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: |

View File

@ -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
View File

@ -58,3 +58,4 @@ pylint.txt
# Ignore Submodule data directory
coriolis-data/
FDevIDs/

3
.gitmodules vendored
View File

@ -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

View File

@ -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

View File

@ -1 +1 @@
3.11.7
3.11.9

View File

@ -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)

View File

@ -679,7 +679,7 @@ the following does not work:
```py
from sys import platform
if platform == 'darwin':
if platform == 'win32':
...
```

16
EDMC.py
View File

@ -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
View 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()

File diff suppressed because it is too large Load Diff

@ -1 +0,0 @@
Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2

View File

@ -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í";

View File

@ -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}";

View File

@ -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}";

View File

@ -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";

View File

@ -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ä";

View File

@ -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";

View File

@ -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";

View File

@ -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}";

View File

@ -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} があります";

View File

@ -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가 활성 창 상태일때만";

View File

@ -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";

View File

@ -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";

View File

@ -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}";

View File

@ -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}";

View File

@ -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}";

View File

@ -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}";

View File

@ -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 ";

View File

@ -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}";

View File

@ -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}";

View File

@ -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";

View File

@ -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}";

View File

@ -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}";

View File

@ -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 在前台运行时生效";

View File

@ -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.

View File

@ -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()

View File

@ -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)}')

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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.
"""

View File

@ -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.";

View File

@ -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.

View File

@ -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',

View File

@ -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()

View File

@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -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)

View File

@ -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
View File

@ -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__":

BIN
modules.p

Binary file not shown.

View File

@ -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())

View File

@ -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)

View File

@ -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
View File

@ -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

View File

@ -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':

View File

@ -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)

View File

@ -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:

View File

@ -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
View File

@ -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")

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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

BIN
ships.p

Binary file not shown.

248
stats.py
View File

@ -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
View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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", "<", ">", ":", '"', "/", "\\", "|", "?", "*")}
)