Merge branch 'main' into expanduser-journal-path

This commit is contained in:
David Muckle authored and GitHub committed 2025-02-27 09:29:09 -05:00
commit 39b0aec5f2
121 files changed
+6317 -4019

No files matched your search

-1
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
+23 -12
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
+13
View File
@@ -0,0 +1,13 @@
# Reporting Security Issues
EDMC takes security very seriously. Our users trust us to provide a secure and safe tool to support their experience in Elite.
In general, the best way to report a major security issue with us that should not be publically discussed is to email our maintainer teams.
The best point of contact for this is edmc@hullseals.space. When contacting, be sure to include as much information in your report.
As soon as your report is processed, we'll get in touch to make sure we quickly move ahead with fixing the issue and will lay out a timeline for public disclosure and fixes.
Another method of reporting vulnerabilities is to open a new Bug Report [here](https://github.com/EDCD/EDMarketConnector/issues/new?assignees=&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title=).
If reporting a security issue here, do not include details as to the issue or steps to reproduce, simply indicate you have found a potential security bug and would like us to contact you directly.
+18
View File
@@ -0,0 +1,18 @@
<!---
Thank you for submitting a PR for EDMC! Please follow this template to ensure your PR is processed.
In general, you should be submitting targeting the develop branch. Please make sure you have this selected.
-->
# Description
<!-- What does this PR Do? -->
# Example Images
<!-- Only if relevant. Remove if irrelevant. -->
# Type of Change
<!-- What type of change is this? New Feature? Enhancement to Existing Feature? Bug Fix? Translation Update? -->
# How Tested
<!-- How have you tested this change to ensure it works and doesn't cause new issues -->
# Notes
<!-- Does this resolve any open issues? Was this PR discussed internally somewhere off GitHub? -->
+95
View File
@@ -0,0 +1,95 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches-ignore:
- 'stable'
- 'releases'
- 'beta'
pull_request:
branches: [ develop ]
schedule:
- cron: '38 5 * * 4'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: 'ubuntu-latest'
timeout-minutes: 360
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
+7 -6
View File
@@ -13,13 +13,13 @@ on:
jobs:
code-checks:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
# Debug show the info we have to work with
- name: Show github context
run: cat $GITHUB_EVENT_PATH
run: cat "$GITHUB_EVENT_PATH"
####################################################################
# Checkout the necessary commits
@@ -44,22 +44,23 @@ jobs:
echo "BASE_REPO_URL: ${BASE_REPO_URL}"
echo "BASE_REPO_OWNER: ${BASE_REPO_OWNER}"
# Add the 'base' repo as a new remote
git remote add ${BASE_REPO_OWNER} ${BASE_REPO_URL}
git remote add "${BASE_REPO_OWNER}" "${BASE_REPO_URL}"
# And then fetch its references
git fetch ${BASE_REPO_OWNER}
git fetch "${BASE_REPO_OWNER}"
####################################################################
####################################################################
# Get Python set up
####################################################################
- name: Set up Python
uses: actions/setup-python@v4
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
pip install wheel flake8
pip install flake8
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; elif [ -f requirements.txt ]; then pip install -r requirements.txt ; fi
####################################################################
+9 -4
View File
@@ -19,20 +19,21 @@ on:
jobs:
push_checks:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
cache: 'pip' # caching pip dependencies
python-version-file: '.python-version'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install wheel flake8 pytest
pip install flake8 pytest
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
- name: Setup flake8 annotations
@@ -42,7 +43,7 @@ jobs:
env:
ROOT_SHA: ${{github.base_ref}}
run: |
DATA=$(jq --raw-output .before $GITHUB_EVENT_PATH)
DATA=$(jq --raw-output .before "$GITHUB_EVENT_PATH")
echo "DATA: ${DATA}"
#######################################################################
@@ -68,3 +69,7 @@ jobs:
- name: mypy type checks
run: |
./scripts/mypy-all.sh --platform win32
- name: translation checks
run: |
python ./scripts/find_localised_strings.py --compare-lang L10n/en.template --directory . --ignore coriolis-data
+1 -1
View File
@@ -54,7 +54,7 @@ jobs:
- name: Create submodules changes branch
if: steps.check_for_changes.outputs.changes == 'true'
run: |
git checkout -b "submodule-change/$GITHUB_RUN_ID" $CHECKOUT_BRANCH
git checkout -b "submodule-change/$GITHUB_RUN_ID" "$CHECKOUT_BRANCH"
git commit -am "updating submodules"
git push --set-upstream origin "submodule-change/$GITHUB_RUN_ID"
+38 -17
View File
@@ -1,5 +1,5 @@
# vim: tabstop=2 shiftwidth=2
name: Build EDMC for Windows
name: Build EDMC
on:
push:
@@ -11,7 +11,7 @@ jobs:
variables:
outputs:
sem_ver: ${{ steps.var.outputs.sem_ver }}
archive_exclusions: ${{ steps.var.outputs.archive_exclusions }}
short_sha: ${{ steps.var.outputs.short_sha }}
runs-on: "ubuntu-latest"
steps:
- name: Setting global variables
@@ -20,6 +20,7 @@ jobs:
with:
script: |
core.setOutput('sem_ver', '${{ github.ref_name }}'.replaceAll('Release\/', ''))
core.setOutput('short_sha', '${{ github.sha }}'.substring(0, 8))
linux_build:
needs: [variables]
@@ -29,18 +30,25 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true
- name: Create .gitversion
run: |
echo "${{ needs.variables.outputs.short_sha }}" > .gitversion
- name: Make tar archive
run: |
# For 'tar' we can only specify filename/glob exclusions, not any
# directory location
tar -c -v -z \
-f ../EDMarketConnector-release-${{ needs.variables.outputs.sem_ver }}.tar.gz \
-f "../EDMarketConnector-release-${{ needs.variables.outputs.sem_ver }}.tar.gz" \
-C .. \
--exclude=EDMarketConnector-release-*.* \
--exclude=.editorconfig \
--exclude=.flake8 \
--exclude=.git* \
--exclude=.gitattributes \
--exclude=.gitignore \
--exclude=.gitmodules \
--exclude=.git \
--exclude=.github \
--exclude=.mypy.ini \
--exclude=.pre-commit-config.yaml \
--exclude=build.py \
@@ -51,19 +59,19 @@ jobs:
--exclude=scripts \
--exclude=tests \
EDMarketConnector
mv ../EDMarketConnector-release-${{ needs.variables.outputs.sem_ver }}.tar.gz .
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:
@@ -93,21 +101,21 @@ jobs:
# presumably due to a Windows CL length limit.
exclusions: 'EDMarketConnector/EDMarketConnector-release-*.* EDMarketConnector/.editorconfig EDMarketConnector/.flake8 EDMarketConnector/.git* EDMarketConnector/.mypy.ini EDMarketConnector/.pre-commit-config.yaml EDMarketConnector/build.py EDMarketConnector/*.manifest EDMarketConnector/coriolis-data/ EDMarketConnector/img/ EDMarketConnector/pyproject.toml EDMarketConnector/scripts/ EDMarketConnector/tests/'
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version-file: '.python-version'
architecture: "x86"
cache: 'pip' # caching pip dependencies
- name: Install python tools
run: |
pip install wheel
pip install -r requirements-dev.txt
- name: Download winsparkle
run: |
Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.8.0/WinSparkle-0.8.0.zip -OutFile out.zip
Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.8.1/WinSparkle-0.8.1.zip -OutFile out.zip
Expand-Archive out.zip
Move-Item 'out\WinSparkle-0.8.0\Release\*' '.\'
Move-Item 'out\WinSparkle-0.8.1\Release\*' '.\'
- name: Build EDMC
run: |
@@ -118,23 +126,36 @@ jobs:
with:
filepath: './EDMC_Installer_Config.iss'
- name: Rename Installer
run: |
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: ./
@@ -143,7 +164,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
+6 -1
View File
@@ -9,8 +9,9 @@ build
dist.win32/
dist.*
# Ignore generated ChangeLog.html file
# Ignore generated ChangeLog files
ChangeLog.html
/scripts/script_output
# Ignore files
dump
@@ -30,6 +31,8 @@ appcast_mac_*.xml
EDMC_Installer_Config.iss
EDMarketConnector.wxs
wix/components.wxs
edmarketconnector.xml
edmarketconnector-beta.xml
# Ignore Visual Elements Manifest file for Windows
EDMarketConnector.VisualElementsManifest.xml
@@ -52,6 +55,8 @@ htmlcov/
.coverage
pylintrc
pylint.txt
.pylintrc
# Ignore Submodule data directory
coriolis-data/
FDevIDs/
-3
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
-1
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
+1 -1
View File
@@ -51,7 +51,7 @@ repos:
- id: mypy
# verbose: true
# log_file: 'pre-commit_mypy.log'
additional_dependencies: [ types-pkg-resources, types-requests, types-urllib3 ]
additional_dependencies: [ types-setuptools, types-requests, types-urllib3 ]
# args: [ "--follow-imports", "skip", "--ignore-missing-imports", "--scripts-are-modules" ]
### # pydocstyle.exe <file>
+1 -1
View File
@@ -1 +1 @@
3.11.1
3.11.9
+377 -26
View File
@@ -1,38 +1,389 @@
This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first).
---
* We currently test against, and package with, Python 3.11.9, 32-bit.
* As a result, we do not support Windows 7, 8, or 8.1.
* Developers can check the contents of the `.python-version` file
in the source (not distributed with the Windows installer) for the
currently used version.
---
Copyrights
Release 5.12.2
===
Please see the [docs/Licenses](docs/Licenses/) directory for copies of any
licenses for software we use with EDMarketConnector, either at runtime, or to
produce the Windows executables and installer.
This is a pre-release to test a number of bugfixes and security improvements in EDMC.
---
**Changes and Enhancements**
* Adds a guard against workflow shell execution vulnerabilities in GitHub Actions
* Adds a "Copy" icon in the EDMC System Profiler
* Includes additional Loadout event properties in the "State" context
* Updates Dependencies and Submodules
* Removes an outdated development script that was no longer in use and prevented dependency updates
* Replaces types-pkg-resources with types-setuptools per PyPi documentation
* We now test against, and package with, Python 3.11.1, 32-bit.
**Bug Fixes**
* Removes Duplicate Coriolis Definitions Included In Submodules
* Adds Context Support for Mandalay and Cobra Mk V, and Type-8 Transporter
* Adds a number of missing modules to modules.json
* Fixes a widely-reported bug where missing HullValue or ModuleValue entries would cause parsing to crash
* Fixes a bug where PSUtils exception handling was not processed
**As a consequence of this we no longer support Windows 7.
This is due to
[Python 3.10.x and later not supporting Windows 7](https://www.python.org/downloads/windows/).
The application (both EDMarketConnector.exe and EDMC.exe) will crash on
startup due to a missing DLL.**
**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()`.
As [Windows 8.1 is now End-Of-Life](https://learn.microsoft.com/en-us/lifecycle/faq/windows#windows-8-1)
we no longer explicitly support it, but for the time being it will likely
continue to work. This is dependent on future Python releases not dropping
support for Windows 8.1 in a manner that prevents it working. Any bug report
made against Windows 8.1 use may be ignored unless reproduced on a supported
OS.
Release 5.12.1
===
This should have no other impact on users or plugin developers, other
than the latter now being free to use features that were introduced since the
Python 3.7 series.
This release fixes a handful of bugs reported with 5.12.0, notably a widely-reported bug with EDMC CAPI Authentication.
Developers can check the contents of the `.python-version` file
in the source (it's not distributed with the Windows installer) for the
currently used version in a given branch.
**Changes and Enhancements**
* Fixed a typo in the prior release notes
**Bug Fixes**
* Fixed a bug where the EDMC System Profiler wouldn't load details properly
* Reverted a number of usages of Pathlib back to os.path for further validation testing
* Fixed a bug where EDMC would error out with a max() ValueError
* Fixed an issue where the EDMC protocol wouldn't be processed properly via prototyping
**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()`.
Release 5.12.0
===
This release brings a number of performance enhancements and functionality updates requested by the community to EDMC.
Notably, integration with Inara's SLEF notation, custom plugin directories, streamlined logging locations, and
performance enhancements are included.
This release also fixes a few administrative issues regarding licenses to ensure compliance with included libraries.
**Changes and Enhancements**
* Added the ability to export a ship's loadout to Inara SLEF notation
* Added the ability for EDMC to restart itself if required after settings changes
* Added the ability to change the custom plugins directory to allow for multiple plugin profiles
* Added Basic Type 8 Support
* Updated the default logging directory from $TEMPDIR or %TEMP% and to the current app data directory
* Updated a number of direct win32API calls to use proper prototyped library calls
* Updated a number of translations
* Updated a number of dependencies
* Updated included and bundled licenses to comply with dependency requirements
* Updated the game_running check to be more efficient on Windows to reduce program hangs
* Minor logic enhancements
* Retired most usages of os.path in favor of the preferred PathLib
**Bug Fixes**
* Fixed a bug that would result in Horizons and Odyssey flags not being passed to EDDN
**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()`.
Release 5.11.3
===
This release fixes a bug where an incomplete hand-over from ordereddict to dict types would cause a sender failure.
**Changes and Enhancements**
* Updated Translations
**Bug Fixes**
* Fixed a bug where two senders might fail due to improper data formats
**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()`.
* FDevID files (`commodity.csv` and `rare_commodity.csv`) have moved their preferred location to the app dir (same location as default Plugins folder). Please migrate to use `config.app_dir_path`.
Release 5.11.2
===
This release fixes a bug where minimizing to the system tray could cause the program to not un-minimize.
**Changes and Enhancements**
* Updated Translations
* Added a developer utility to help speed up changelog development
**Bug Fixes**
* Fixed a bug where minimizing to the system tray could cause the program to not un-minimize.
**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()`.
* FDevID files (`commodity.csv` and `rare_commodity.csv`) have moved their preferred location to the app dir (same location as default Plugins folder). Please migrate to use `config.app_dir_path`.
Release 5.11.1
===
This release fixes a bug regarding FDevID files when running from Source in a non-writable location. Additionally,
Deprecation Warnings are now more visible to aid in plugin development.
**Changes and Enhancements**
* Added a check on Git Pushes to check for updated translation strings for developers
* Enabled deprecation warnings to pass to plugins and logs
* Updated Dependencies
* Replaced infi.systray with drop-in replacement simplesystray
**Bug Fixes**
* Fixed a bug that could result in the program not updating or writing FDevID files when running from source in a location where the running user can't write to
**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()`.
* FDevID files (`commodity.csv` and `rare_commodity.csv`) have moved their preferred location to the app dir (same location as default Plugins folder). Please migrate to use `config.app_dir_path`.
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.
This should represent full support for the new Python Mk II.
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
**Changes and Enhancements**
* Added new SCO Module Details
* Reverted a change from the prior release due to breaking some consumers.
**Plugin Developers**
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
**Plugin Developers**
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
Release 5.10.5
===
This release contains a fix for a bug that could crash EDMC's console versions when reading outfitting information
from the new SCO Frame Shift Drive modules.
Please note that this does not offer full support for the new SCO modules or the Python Mk II. More support will
be added in a future update.
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
**Changes and Enhancements**
* Updated Translations
* Added limited data regarding the Python Mk II
* Added a few Coriolis module information entries
**Bug Fixes**
* Fixed a bug that could cause the new SCO modules to display improper ratings or sizes
* Fixed a bug where the new SCO modules would display as a normal Frame Shift Drive
* Fixed a bug which could crash EDMC if the exact details of a Frame Shift Drive were unknown
**Plugin Developers**
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
Release 5.10.4
===
This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also
adds Turkish translations to EDMC!
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
**Changes and Enhancements**
* Adds Turkish Translations to EDMC
* Adds DockingDenied and DockingGranted EDDN Schemas
* Updated FDevIDs Dependency
* Updated Translations
* Updated modules files to process several missing module types used for bug squishing or going fast
* Updated Python Dependencies
**Bug Fixes**
* Fixed a bug on older Python versions which couldn't import updated type annotations
**Plugin Developers**
* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
Release 5.10.3
===
This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations.
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
**Changes and Enhancements**
* Updated French Translations
**Bug Fixes**
* Fixed a bug that crashed the outfitting system when encountering armor. (Thanks TCE team for identifying this one!)
**Plugin Developers**
* modules.p and ships.p are deprecated, and slated
for removal in the next major release! Please look for that change coming soon.
* Note to plugin developers: The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in the next major release! Please migrate to `webbrowser.open()`.
Release 5.10.2
===
This release contains updated dependencies, some bug fixes, a few minor enhancements to some supporting files,
and some resorted resources as well as a new image for some of the built EXEs.
We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
**Changes and Enhancements**
* Added additional logging to the Python build string in the case of missing files
* Added a new icon to EDMC's Command-Line EXE
* Added additional logging to the build system
* Updated several dependencies
* Updated FDEV IDs
* Updated relevant copyright dates
* Updated automatic build script to support code signing workflow
* Updated translations to the latest versions
* Moved a few unused files to the resources folder. These files have no references in the code
**Bug Fixes**
* Fixed a bug that could cause EDMC to handle SIGINT signals improperly
* Fixed a bug that could result in URL providers to be set to invalid values
* Fixed a bug that could result in Coriolis URL providers to revert back to "Auto" on language translations
* Fixed a bug where Inara didn't understand being blown up by a Thargoid, and blew itself up instead
* Fixed a printing issue for the localization system for unused strings
**Removed Files**
* Removed two unused manifest and MacOS icon files which are no longer in use.
**Known Issues**
* Some users of TCE have reported issues with newer versions of EDMC with TCE.
* We have been unable to replicate this issue. If you are able to assist, please
add your information here: https://github.com/EDCD/EDMarketConnector/issues/2176
**Plugin Developers**
* modules.p and ships.p are deprecated, and slated
for removal in the next major release! Please look for that change coming soon.
* Note to plugin developers: The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in the next major release! Please migrate to `webbrowser.open()`.
Release 5.10.1
===
This release contains a number of bugfixes, minor performance enhancements,
workflow and dependency updates, and a function deprecation.
Note to plugin developers: modules.p and ships.p are deprecated, and slated
for removal in the next major release! Please look for that change coming soon.
Note to plugin developers: The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in the next major release! Please migrate to `webbrowser.open()`.
**Changes and Enhancements**
* Deprecated `openurl()`. Please migrate to `webbrowser.open()`
* Updated a number of list comparisons to use more efficient tuple comparisons
* Updated a few type hints
* Updated a few binary comparitors to be more efficient
* Moved `resources.json` and `modules.json` back to the top level for all users
* Updated several dependencies
* Updated Python version to 3.11.7
**Bug Fixes**
* Fixed an issue where resources files could be in different locations for different users.
* These files are now in the same location (top level) for all users on all distributions.
* Fixed an issue where CMDRs without the Git application installed would crash on start if running from Source.
* Thanks to the Flatpak team for pointing this one out!
* Fixed a bug where CMDRs running from source would have their git hash version displayed as UNKNOWN.
* We're now more failure tolerant and use the bundled .gitversion if no true git hash is provided.
* Fixed a bug where starting two copies of EDMC with a valid install would not generate a duplicate warning.
---
Release 5.10.0
===
This release contains a number of under-the-hood changes to EDMC designed to improve performance, code
@@ -43,7 +394,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
@@ -109,7 +460,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)
+1 -1
View File
@@ -679,7 +679,7 @@ the following does not work:
```py
from sys import platform
if platform == 'darwin':
if platform == 'win32':
...
```
+11 -5
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)
+12 -12
View File
@@ -26,12 +26,13 @@ To utilise logging in core code, or internal plugins, include this:
To utilise logging in a 'found' (third-party) plugin, include this:
import os
from pathlib import Path
import logging
plugin_name = os.path.basename(os.path.dirname(__file__))
# Retrieve the name of the plugin folder
plugin_name = Path(__file__).resolve().parent.name
# Set up logger with hierarchical name including appname and plugin_name
# plugin_name here *must* be the name of the folder the plugin resides in
# See, plug.py:load_plugins()
logger = logging.getLogger(f'{appname}.{plugin_name}')
"""
from __future__ import annotations
@@ -41,7 +42,7 @@ import logging
import logging.handlers
import os
import pathlib
import tempfile
import warnings
from contextlib import suppress
from fnmatch import fnmatch
# So that any warning about accessing a protected member is only in one place.
@@ -50,9 +51,7 @@ from threading import get_native_id as thread_native_id
from time import gmtime
from traceback import print_exc
from typing import TYPE_CHECKING, cast
import config as config_mod
from config import appcmdname, appname, config
from config import appcmdname, appname, config, trace_on
# TODO: Tests:
#
@@ -99,9 +98,11 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type
# MAGIC-CONT: See MAGIC tagged comment in Logger.__init__()
logging.Formatter.converter = gmtime
warnings.simplefilter('default', DeprecationWarning)
def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None:
if any(fnmatch(condition, p) for p in config_mod.trace_on):
if any(fnmatch(condition, p) for p in trace_on):
self._log(logging.TRACE, message, args, **kwargs) # type: ignore # we added it
return
@@ -181,8 +182,7 @@ class Logger:
# We want the files in %TEMP%\{appname}\ as {logger_name}-debug.log and
# rotated versions.
# This is {logger_name} so that EDMC.py logs to a different file.
logfile_rotating = pathlib.Path(tempfile.gettempdir())
logfile_rotating /= f'{appname}'
logfile_rotating = pathlib.Path(config.app_dir_path / 'logs')
logfile_rotating.mkdir(exist_ok=True)
logfile_rotating /= f'{logger_name}-debug.log'
@@ -486,8 +486,8 @@ class EDMCContextFilter(logging.Filter):
:return: The munged module_name.
"""
file_name = pathlib.Path(frame_info.filename).expanduser()
plugin_dir = pathlib.Path(config.plugin_dir_path).expanduser()
internal_plugin_dir = pathlib.Path(config.internal_plugin_dir_path).expanduser()
plugin_dir = config.plugin_dir_path.expanduser()
internal_plugin_dir = config.internal_plugin_dir_path.expanduser()
# Find the first parent called 'plugins'
plugin_top = file_name
while plugin_top and plugin_top.name != '':
+206
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 \U0001F5D0",
command=lambda: copy_sys_report(root, sys_report),
)
sys_report_btn.grid(row=3, column=0, padx=20, pady=10, sticky="w")
github_btn = ttk.Button(
root,
text="Open GitHub Bug Report",
command=lambda: webbrowser.open(
"https://github.com/EDCD/EDMarketConnector/issues/new?assignees="
"&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title="
),
)
github_btn.grid(row=3, column=0, padx=20, pady=10, sticky="e")
# Update and get window dimensions
root.update()
width = root.winfo_reqwidth() + 20
height = root.winfo_reqheight() + 20
# Set window size and show
root.geometry(f"{width}x{height}")
root.title("EDMarketConnector")
root.deiconify()
root.resizable(False, False)
root.mainloop()
if __name__ == "__main__":
# Args: Only work if not frozen
parser = argparse.ArgumentParser(
prog=appname,
description="Prints diagnostic and debugging information about the current EDMC configuration.",
)
parser.add_argument(
"--out-console",
help="write the system information to the console",
action="store_true",
)
args = parser.parse_args()
# Suppress Logger
logger = get_main_logger()
logger.setLevel(logging.CRITICAL)
if getattr(sys, "frozen", False):
sys.stderr._error = "inhibit log creation" # type: ignore
cur_config = config.get_config()
if args.out_console:
sys_report = get_sys_report(cur_config)
print(sys_report)
sys.exit(0)
main()
@@ -1,6 +0,0 @@
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<VisualElements
BackgroundColor="black"
ShowNameOnSquare150x150Logo="on"
ForegroundText="light" />
</Application>
Binary file not shown.
+365 -400
View File
File diff suppressed because it is too large. Load diff
Submodule FDevIDs deleted from 069b09b1af.
+3 -30
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";
@@ -178,13 +172,13 @@
/* 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" = "Výchozí";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normal";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -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í";
@@ -370,9 +346,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Složka pluginů";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Otevřít";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: Plugin můžete vypnout přidáním{CR}'{EXT}' do názvu jeho složky";
+62 -41
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" = "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 +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" = "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 +93,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 +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" = "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,11 +177,11 @@
/* 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: 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";
/* 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: 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 commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
"CAPI: No commander data returned" = "CAPI: Keine Kommandanten-Daten erhalten";
@@ -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." = "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";
@@ -231,6 +237,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." = "Eins oder mehr deiner aktivierten Plugins konnte nicht geladen werden. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Dies könnte an einer falschen Ordnerstruktur liegen. Die Datei load.py sollte sich unter plugins/PLUGIN_NAME/load.py befinden.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst.";
/* 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" = "Einer oder mehrere deiner URL-Anbieter waren ungültig und wurden zurückgesetzt:\n\n";
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} war zuvor auf {OLDPROV} festgelegt und wurde zu {NEWPROV} zurückgesetzt.\n";
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
"EDMC: Default Providers Reset" = "EDMC: Standardanbieter wurden zurückgesetzt";
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
"Awaiting Full CMDR Login" = "Warte auf vollständige CMDR Anmeldung";
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
"Journal directory already locked" = "Journal-Ordner bereits gesperrt";
@@ -246,13 +264,13 @@
/* 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" = "Standard";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normal";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -339,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}" = "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";
@@ -360,9 +375,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...";
@@ -376,23 +388,11 @@
"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";
"Enable Fleet Carrier CAPI Queries" = "Carrier-CAPI-Anfragen aktivieren";
/* 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";
@@ -468,9 +468,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Plugin-Ordner";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Öffnen";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tipp: Du kannst ein Plugin deaktivieren, indem{CR}du '{EXT}' zu seinem Ordnernamen hinzufügst";
@@ -489,6 +486,9 @@
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Deaktivierte Plugins";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Fehler im System Profiler";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Kontostand";
@@ -768,3 +768,24 @@
/* stats.py: Status dialog title; In files: stats.py:418; */
"Ships" = "Schiffe";
/* 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}";
+75 -40
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";
@@ -231,6 +237,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." = "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.";
/* 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" = "One or more of your URL Providers were invalid, and have been reset:\r\n\r\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} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n";
/* 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";
@@ -246,13 +264,13 @@
/* 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" = "Default";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normal";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -339,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";
@@ -360,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...";
@@ -376,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";
@@ -468,8 +468,11 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Plugins folder";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Open";
/* prefs.py: Label on button used to open the Plugin Folder; */
"Open Plugins Folder" = "Open Plugins Folder";
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
"Plugin Directory Location" = "Plugin Directory Location";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"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";
@@ -489,6 +492,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";
@@ -789,3 +795,32 @@
/* 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?";
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
"Restart Required" = "Restart Required";
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
"A restart of EDMC is required. EDMC will now restart." = "A restart of EDMC is required. EDMC will now restart.";
/* 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}";
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
"Copy Inara SLEF" = "Copy Inara SLEF";
+3 -30
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";
@@ -184,13 +178,13 @@
/* 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" = "Por defecto";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normal";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -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";
@@ -385,9 +361,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Carpeta de Plugins";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Abrir";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Puedes desactivar un plugin añadiendo{CR}'{EXT}' al nombre de su carpeta";
-27
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ä";
@@ -343,9 +319,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Lisäosien hakemistosijainti";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Avaa";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Vihje: Voit poistaa lisäosan käytöstä{CR}lisäämällä '{EXT}' sen hakemistonimeen";
+44 -27
View File
@@ -1,21 +1,59 @@
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
"Send flight log and CMDR status to EDSM" = "Envoyer les données de vol et le status de CMDR à EDSM";
/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
"Open Log Folder" = "Ouvrir le dossier de log";
/* inara.py:Text Inara Show API key; In files: inara.py:305; */
"Show API Key" = "Afficher la clé API";
/* Language name */
"!Language" = "Français";
/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */
"Error: Frontier CAPI didn't respond" = "L'API Compagnon de Frontier ne répond pas";
/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */
"Error: Frontier server is lagging" = "Erreur : Le serveur Frontier ne répond pas";
/* 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?" = "Amarré, mais station inconnue : colonie EDO ?";
/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */
"Error: Invalid Credentials" = "Erreur : Identifiants invalides";
/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
"Error: Wrong Cmdr" = "Erreur : Cmdr incorrect";
/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
"Error" = "Erreur";
/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */
"Error: Couldn't check token customer_id" = "Erreur : Impossible de vérifier le jeton customer_id";
/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */
"Error: customer_id doesn't match!" = "Erreur : customer_id ne correspond pas !";
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
"Error: unable to get token" = "Erreur : impossible d'obtenir le jeton";
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
"Frontier CAPI down for maintenance" = "L'API Compagnon de Frontier est en panne pour maintenance";
/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
"Frontier CAPI query failure" = "Échec de la requête à l'API Compagnon Frontier";
/* 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" = "Mettre à jour";
/* 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" = "Toujours visible";
/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
"Unknown" = "Inconnue";
/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
"Error: Check E:D journal file location" = "Erreur : Vérifier l'emplacement du fichier de journal Elite : Dangerous";
/* 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" = "Cmd";
@@ -40,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";
@@ -64,6 +96,12 @@
/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
"Documentation" = "Documentation";
/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
"Troubleshooting" = "Résolution de problème";
/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
"Report A Bug" = "Signaler un bug";
/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
"Privacy Policy" = "Politique de Confidentialité";
@@ -178,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";
@@ -199,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...";
@@ -211,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";
@@ -271,9 +291,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Répertoire des plugins";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Ouvrir";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Astuce : Vous pouvez désactiver un plugin en{CR}ajoutant '{EXT}' à son nom de répertoire";
-27
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";
@@ -259,9 +235,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Plugin mappa";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Nyisd ki";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tipp:a pluginokat kikapcsolhatod hozzà{CR}adsz a mappa nevében egy '{EXT}'";
+93 -35
View File
@@ -1,3 +1,11 @@
/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
"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" = "Apri la Cartella dei Log";
/* inara.py:Text Inara Show API key; In files: inara.py:305; */
"Show API Key" = "Mostra l'API Key";
/* Language name */
"!Language" = "Italiano";
@@ -28,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";
@@ -70,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";
@@ -85,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...";
@@ -115,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" = "Copia";
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
"Cut" = "Taglia";
/* myNotebook.py: Label for 'Paste' as in 'Copy and Paste'; */
"Paste" = "Incolla";
/* myNotebook.py: Label for 'Select All'; */
"Select All" = "Seleziona Tutto";
/* 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" = "Autenticazione CAPI disabilitata dal killswitch";
@@ -157,11 +177,11 @@
/* 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: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
"CAPI: No Fleet Carrier data returned" = "CAPI: nessun dato della Fleet Carrier ottenuto";
/* 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: We didn't have the Fleet Carrier callsign when we should have; In files: EDMarketConnector.py:1223; */
"CAPI: Fleet Carrier data incomplete" = "CAPI: dati della Fleet Carrier 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";
@@ -211,6 +231,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: Plugins senza Supporto 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: plugin non funzionanti";
/* 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." = "Impossibile caricare uno o più plugin abilitati. Consulta l'elenco nella scheda '{PLUGINS}' di '{FILE}' > '{SETTINGS}'. Ciò potrebbe essere causato da un'errata struttura delle cartelle. Il file load.py dovrebbe trovarsi in plugins/PLUGIN_NAME/load.py.\n\nPuoi disabilitare un plugin rinominando la sua cartella in modo che abbia '{DISABLED}' alla fine del nome.";
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Uno o più Provider URL non erano validi e sono stati ripristinati:\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 impostato su {OLDPROV} ed è stato reimpostato su {NEWPROV}\n";
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
"EDMC: Default Providers Reset" = "EDMC: Reset dei provider predefiniti";
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
"Awaiting Full CMDR Login" = "In attesa del login completo al CMDR";
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
"Journal directory already locked" = "cartella Journal già bloccata";
@@ -226,13 +264,13 @@
/* 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" = "Predefinito";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normale";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -319,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}" = "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";
@@ -340,9 +375,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...";
@@ -356,23 +388,11 @@
"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";
"Enable Fleet Carrier CAPI Queries" = "Abilita le query CAPI per le Fleet Carrier";
/* 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";
@@ -448,8 +468,11 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Cartella dei plugins";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Sfoglia";
/* prefs.py: Label on button used to open the Plugin Folder; */
"Open Plugins Folder" = "Apri la cartella dei plugin";
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
"Plugin Directory Location" = "Posizione della Cartella del Plugin";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Suggerimento: è possibile disabilitare un plugin {CR}aggiungendo '{EXT}' nel nome della sua cartella";
@@ -463,9 +486,15 @@
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
"Information on migrating plugins" = "Informazioni sulla migrazione dei plugins";
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
"Broken Plugins" = "Plugin non funzionanti";
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Plugin disabilitati";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Errore nel Sistema di Profilazione";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Saldo";
@@ -766,3 +795,32 @@
/* stats.py: Status dialog title; In files: stats.py:418; */
"Ships" = "Navi";
/* update.py: Update Available Text; In files: update.py:229; */
"{NEWVER} is available" = "{NEWVER} è disponibile";
/* prefs.py: Stable Version of EDMC; */
"Stable" = "Stabile";
/* prefs.py: Select the Update Track (Beta, Stable); */
"Update Track" = "Canale di aggiornamento";
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
"Update Track Changed to {TRACK}" = "Canale di aggiornamento cambiato in {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?" = "Il canale di aggiornamento è cambiato da Beta a Stabile. Non riceverai più aggiornamenti Beta. Rimarrai sulla tua attuale versione Beta fino alla prossima release Stabile.\n\nPuoi tornare manualmente all'ultima versione Stabile. Per farlo, devi scaricare e installare manualmente l'ultima versione Stabile. Nota che questo potrebbe introdurre bug o causare malfunzionamenti se si esegue il downgrade tra versioni principali con cambiamenti significativi.\n\nVuoi aprire GitHub per scaricare l'ultima release?";
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
"Restart Required" = "Riavvio necessario";
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
"A restart of EDMC is required. EDMC will now restart." = "È necessario riavviare EDMC. EDMC si riavvierà ora.";
/* 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}";
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
"Copy Inara SLEF" = "Copia Inara SLEF";
+62 -41
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\nGitHubにバグレポートを作成しますか?";
/* 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,11 +177,11 @@
/* 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: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
"CAPI: No Fleet Carrier 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: 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 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." = "有効にしている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" = "プラグイン";
@@ -231,6 +237,18 @@
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "有効にしている1つ以上のプラグインがロードに失敗しました。'{FILE}' > '{SETTINGS}' メニューで表示される設定ダイアログの'{PLUGINS}' タブの一覧を確認してください。この問題は誤ったフォルダ構造によって引き起こされます。load.pyファイルはplugins/プラグイン名/plug-in.pyとして配置される必要があります。\n\nプラグインを無効にするにはフォルダ名の最後に'{DISABLED}'を追加してください。";
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "一つ以上のURLプロバイダが無効であったためリセットされました:\n\n";
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} は {OLDPROV} に設定され、 {NEWPROV} にリセットされました\n";
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
"EDMC: Default Providers Reset" = "EDMC: デフォルトプロバイダーのリセット";
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
"Awaiting Full CMDR Login" = "完全なCMDRログインを待機しています";
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
"Journal directory already locked" = "ジャーナルディレクトリは既にロックされています";
@@ -246,13 +264,13 @@
/* 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:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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:76; coriolis.py:122; coriolis.py:138; */
/* 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; */
@@ -339,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" = "保存するデータの種類を選択して下さい";
@@ -360,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..." = "参照...";
@@ -376,23 +388,11 @@
"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" = "ショートカット";
"Enable Fleet Carrier 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がアクティブの時だけ有効";
@@ -468,9 +468,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "プラグインフォルダ";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "開く";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "ヒント: プラグインを無効にするには{CR}フォルダ名に '{EXT}' を追加してください";
@@ -489,6 +486,9 @@
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "無効なプラグイン";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "システムプロファイラーでエラー";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "口座残高";
@@ -789,3 +789,24 @@
/* 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\nGitHubを開いて最新リリースをダウンロードしますか?";
/* 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} で開く";
+3 -30
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" = "도움말";
@@ -202,13 +196,13 @@
/* 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:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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:76; coriolis.py:122; coriolis.py:138; */
/* 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; */
@@ -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가 활성 창 상태일때만";
@@ -412,9 +388,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "플러그인 폴더";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "열기";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "팁: 플러그인을 비활성화하려면{CR} '{EXT}'를 폴더명 뒤에 추가하면 됨";
-27
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";
@@ -244,9 +220,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Spraudņu mape";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Atvērt";
/* prefs.py: Label on list of enabled plugins; In files: prefs.py:934; */
"Enabled Plugins" = "Iespējoti Spraudņi";
+122 -27
View File
@@ -1,3 +1,11 @@
/* 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 +18,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 +66,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 +75,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 +87,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 +108,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 +135,33 @@
/* 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 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 +171,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 +195,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 +207,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 +288,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 +306,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 +315,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";
@@ -271,9 +366,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Plugins map";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Openen";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: Je kan een plugin deactiveren door '{EXT}'{CR}toe te voegen aan de map naam";
@@ -289,6 +381,9 @@
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Gedeactiveerde plugins";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Fout in systeemprofiler";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Balans";
+27 -39
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,6 @@
/* 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";
@@ -219,6 +207,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: Pluginy Nie Wspierające Python 3.x";
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
"EDMC: Broken Plugins" = "EDMC: Uszkodzone pluginy";
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jeden lub więcej plugin nie został załadowany. Sprawdź listę na zakładce '{PLUGINS}' w menu '{FILE}' > '{SETTINGS}'. Może to być spowodowane błedną strukturą katalogów. Plik load.py powinien być umiezczony w plugins/NAZWA PLUGINA/load.py.\n\nMożesz wyłączyć plugin zmieniając nazwę jego folderu tak, aby zawierała '{DISABLED}' na końcu nazwy.";
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Jeden lub więcej adres URL jest błędny i został zresetowany:\n";
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} ustawiony na {OLDPROV} i został zresetowany do {NEWPROV}\n";
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
"EDMC: Default Providers Reset" = "EDMC: Dostawca domyślny zresetowany";
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
"Awaiting Full CMDR Login" = "Oczekiwanie na pełne zalogowanie do gry";
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
"Journal directory already locked" = "Katalog dziennika zablokowany";
@@ -234,13 +240,13 @@
/* 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" = "Domyślne";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normalny";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -327,9 +333,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ć";
@@ -348,9 +351,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...";
@@ -363,24 +363,9 @@
/* 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ą";
@@ -456,9 +441,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Folder pluginów";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Otwórz";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Wskazówka: Możesz wyłączyć pluginy{CR}dodając '{EXT}' do nazwy folderu";
@@ -471,6 +453,9 @@
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
"Information on migrating plugins" = "Informacja o migracji pluginów";
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
"Broken Plugins" = "Niedziałające pluginy";
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Pluginy wyłączone";
@@ -774,3 +759,6 @@
/* stats.py: Status dialog title; In files: stats.py:418; */
"Ships" = "Statki";
/* update.py: Update Available Text; In files: update.py:229; */
"{NEWVER} is available" = "Dostępna nowa wersja {NEWVER}";
+85 -35
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 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 +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" = "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 +93,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 +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" = "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,11 +177,11 @@
/* 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: 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";
/* 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: 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 commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
"CAPI: No commander data returned" = "CAPI: Nenhum dado de comandante retornado";
@@ -219,6 +231,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: Plugins sem Suporte ao Python 3.x";
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
"EDMC: Broken Plugins" = "EDMC: Plugins Quebrados";
/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Um ou mais plugins ativados falhou ao carregar. Verifique a lista na aba '{PLUGINS}', em '{FILE}' > '{SETTINGS}'. Isto pode ter ocorrido por um erro na estrutura de pastas. O arquivo load.py deve estar localizado em plugins/NOME_PLUGIN/load.py.\n\nVocê pode desativar plugins renomeando a pasta para ter '.{DISABLED}' ao final do nome.";
/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Um ou mais dos seus Provedores de URL eram inválidos e portanto foram reconfigurados:\n";
/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} estava como {OLDPROV}, e foi reconfigurado para {NEWPROV}\n";
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
"EDMC: Default Providers Reset" = "EDMC: Provedores Padrão Reconfigurados";
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
"Awaiting Full CMDR Login" = "Aguardando CMDT entrar no jogo";
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
"Journal directory already locked" = "Diretório de Jornais já está bloqueado";
@@ -234,13 +264,13 @@
/* 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" = "Padrão";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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" = "Automático";
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normal";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -327,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}" = "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";
@@ -340,7 +367,7 @@
"Market data in Trade Dangerous format file" = "Dados de mercado no arquivo de formato Trade Dangerous";
/* prefs.py: Settings > Output option; In files: prefs.py:360; */
"Ship loadout" = "Modulação da nave";
"Ship loadout" = "Build da Nave";
/* prefs.py: Settings > Output option; In files: prefs.py:370; */
"Automatically update on docking" = "Atualizar automaticamente na docagem";
@@ -348,9 +375,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...";
@@ -364,23 +388,11 @@
"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";
"Enable Fleet Carrier CAPI Queries" = "Ativar requisições CAPI para porta-frotas";
/* 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";
@@ -456,8 +468,11 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Pasta dos plugins";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Abrir";
/* prefs.py: Label on button used to open the Plugin Folder; */
"Open Plugins Folder" = "Abrir pasta dos plugins";
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
"Plugin Directory Location" = "Localização da pasta dos plugins";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Dica: Você pode desativar um plugin{CR}adicionando '{EXT}' ao nome da pasta";
@@ -471,9 +486,15 @@
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
"Information on migrating plugins" = "Informações de migração de plugins";
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
"Broken Plugins" = "Plugins Quebrados";
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Plugins desabilitados";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Erro no Perfil de Sistema";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Balanço";
@@ -774,3 +795,32 @@
/* stats.py: Status dialog title; In files: stats.py:418; */
"Ships" = "Naves";
/* update.py: Update Available Text; In files: update.py:229; */
"{NEWVER} is available" = "{NEWVER} está disponível";
/* 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?";
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
"Restart Required" = "Reinicialização necessária";
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
"A restart of EDMC is required. EDMC will now restart." = "É necessário reinicializar o EDMC. Isso será realizado agora.";
/* 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}";
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
"Copy Inara SLEF" = "Copiar formato SLEF";
+62 -41
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...";
@@ -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" = "Copiar";
/* myNotebook.py: Label for 'Cut' as in 'Cut and Paste'; */
"Cut" = "Cortar";
/* 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 desligada por botão";
@@ -165,11 +177,11 @@
/* 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: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
"CAPI: No Fleet Carrier 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: 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 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 +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." = "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 +237,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";
@@ -246,13 +264,13 @@
/* 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" = "Predefinido";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normal";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -339,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}" = "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 +375,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...";
@@ -376,23 +388,11 @@
"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";
"Enable Fleet Carrier CAPI Queries" = "Ligar Consultas CAPI de Transportador de Frota";
/* 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";
@@ -468,9 +468,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Pasta dos Plugins";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Abrir";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Dica: Pode desactivar um plugin{CR}ao adicionar '{EXT}' ao nome da sua pasta";
@@ -489,6 +486,9 @@
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Plugins Desactivados";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Erro nas Informações do Sistema";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Balanço";
@@ -789,3 +789,24 @@
/* 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.";
/* prefs.py: Stable Version of EDMC; */
"Stable" = "Estável";
/* prefs.py: Select the Update Track (Beta, Stable); */
"Update Track" = "Caminho de Actualizações";
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
"Update Track Changed to {TRACK}" = "Caminho de Actualizações alterado 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 Actualizações alterado de Estável para Beta. Deixará de receber actualizações Beta. Irá continuar na versão Beta até à próxima actualização Estável.\n\nPode, manualmente, reverter para a última versão Estável. Para isso, faça download e instale a última versão Estável manualmente. Note que o retrocesso para versões anteriores com diferenças significativas poderá introduzir bugs ou danificar a instalação.\n\nDeseja 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 conteúdo texto.";
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
"Open in {URL}" = "Abrir em {URL}";
+84 -34
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,11 +177,11 @@
/* 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: No data was returned for the Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
"CAPI: No Fleet Carrier 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: 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 commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
"CAPI: No commander data returned" = "CAPI: Нет данных пилота";
@@ -219,6 +231,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-провайдеров оказались некорректными и были сброшены:";
/* 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}";
/* 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" = "Каталог журнала уже заблокирован";
@@ -234,13 +264,13 @@
/* 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:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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:76; coriolis.py:122; coriolis.py:138; */
/* 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; */
@@ -327,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" = "Выберите какие данные стоит сохранять";
@@ -348,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..." = "Просмотреть...";
@@ -364,23 +388,11 @@
"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" = "Сочетание клавиш";
"Enable Fleet Carrier 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 активно";
@@ -456,8 +468,11 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Папка плагинов";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Открыть";
/* prefs.py: Label on button used to open the Plugin Folder; */
"Open Plugins Folder" = "Открыть папку плагинов";
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
"Plugin Directory Location" = "Месторасположение директории плагинов";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Подсказка: Отключить конкретный плагин можно {CR} добавив '{EXT}' к названию папки";
@@ -471,9 +486,15 @@
/* 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" = "Отключенные плагины";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Ошибка в системном профайлере";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Баланс";
@@ -774,3 +795,32 @@
/* 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, чтобы загрузить последнюю версию?";
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
"Restart Required" = "Требуется перезапуск";
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
"A restart of EDMC is required. EDMC will now restart." = "Требуется перезапуск EDMC. EDMC сейчас перезапустится.";
/* 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}";
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
"Copy Inara SLEF" = "Скопировать данные SLEF Inara";
-24
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 ";
+66 -45
View File
@@ -1,5 +1,5 @@
/* 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 +22,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 +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 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 +61,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 +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" = "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 +93,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 +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" = "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,11 +177,11 @@
/* 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: 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 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: 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 commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
"CAPI: No commander data returned" = "CAPI: Nema podataka o komandantu";
@@ -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." = "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 +237,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";
@@ -246,13 +264,13 @@
/* 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" = "Standardna";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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" = "Automatski";
/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normalni";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -325,7 +343,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 +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}" = "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 +375,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...";
@@ -376,23 +388,11 @@
"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";
"Enable Fleet Carrier CAPI Queries" = "Omogući Fleet Carrier CAPI upite";
/* 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";
@@ -468,9 +468,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Folder za dodatke (plugins)";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Otvori";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Savjet: Možete da deaktivirate dodatak (plugin){CR}dodavanjem '{EXT}' na ime njegovog foldera";
@@ -489,6 +486,9 @@
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Deaktivirani dodaci (plugins)";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Greša u System Profileru";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Balans";
@@ -789,3 +789,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}";
+42 -39
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,6 @@
/* 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";
@@ -219,6 +216,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: Dodaci (plugins) bez Python 3.x podrške";
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
"EDMC: Broken Plugins" = "EDMC: Pokvareni Pluginovi";
/* 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 vaših aktivnih pluginova se nije učitao. Molimo pogledajte listu na '{PLUGINS}' tabu u okviru '{FILE}' > '{SETTINGS}'. Obo vi moglo da se desi zbog pogrešne strukture foldera. load.py fajl bi trebao da se nalazi unutar plugins/PLUGIN_NAME/load.py.\n\nPojedinačni plugin možete deaktivirati promenom imena njegovog foldera dodavanjem '{DISABLED}' na kraju.";
/* 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 vaših URL Provajdera su pogrešni, pa 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} i sada je resetovan na {NEWPROV}\n";
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
"EDMC: Default Providers Reset" = "EDMC: Podrazumevani Provajderi 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" = "Žurnal direktorijum je već zaključan";
@@ -234,13 +249,13 @@
/* 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" = "Podrazumevano";
/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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" = "Normal";
/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
/* 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: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -327,9 +342,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";
@@ -348,9 +360,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...";
@@ -363,24 +372,9 @@
/* 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";
@@ -456,9 +450,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Folder za dodatke (plugins)";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Otvori";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: Možete deaktivirati dodatak (plugin) dodavanjem{CR}'{EXT}' na ime njegovog foldera";
@@ -471,6 +462,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" = "Informacije o migraciji dodataka (plugins)";
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
"Broken Plugins" = "Pokvareni pluginovi";
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Deaktivirani dodaci (plugins)";
@@ -774,3 +768,12 @@
/* 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";
/* 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}";
-27
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";
@@ -265,9 +241,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Mapp för plugins";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Öppna";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tips: du kan avaktivera en plugin genom{CR}att lägga till '{EXT}' på slutet av mappens namn";
+812
View File
@@ -0,0 +1,812 @@
/* 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";
/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
"Open Log Folder" = "Günlük Klasörünü Aç";
/* inara.py:Text Inara Show API key; In files: inara.py:305; */
"Show API Key" = "API Anahtarını Göster";
/* Language name */
"!Language" = "Türkçe";
/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */
"Error: Frontier CAPI didn't respond" = "Hata: Frontier CAPI yanıt vermedi";
/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */
"Error: Frontier server is lagging" = "Hata: Frontier sunucusu gecikmesi";
/* 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?" = "Kenetlendi ancak bilinmeyen istasyon: EDO yerleşimi?";
/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */
"Error: Invalid Credentials" = "Hata: Geçersiz Kimlik";
/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
"Error: Wrong Cmdr" = "Hata: Yanlış CMDR";
/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
"Error" = "Hata";
/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */
"Error: Couldn't check token customer_id" = "Hata: token kontrol edilemedi customer_id";
/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */
"Error: customer_id doesn't match!" = "Hata: customer_id uyuşmuyor!";
/* 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";
/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
"Frontier CAPI query failure" = "Frontier CAPI sorgulama hatası";
/* 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" = "Güncelle";
/* 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" = "Her zaman üstte";
/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
"Unknown" = "Bilinmeyen";
/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
"Error: Check E:D journal file location" = "Hata: E:D günlük dosyası konumunu kontrol edin";
/* 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";
/* 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" = "Rol";
/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:915; EDMarketConnector.py:1497; EDMarketConnector.py:1520; stats.py:405; */
"Ship" = "Gemi";
/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:916; */
"Suit" = "Süit";
/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:917; prefs.py:614; stats.py:407; */
"System" = "Sistem";
/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:918; prefs.py:632; prefs.py:770; stats.py:408; */
"Station" = "İstasyon";
/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:921; EDMarketConnector.py:939; EDMarketConnector.py:942; EDMarketConnector.py:2264; */
"File" = "Dosya";
/* 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: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
"Help" = "Yardım";
/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:928; EDMarketConnector.py:959; EDMarketConnector.py:1804; */
"About {APP}" = "Hakkında {APP}";
/* 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...";
/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:932; EDMarketConnector.py:947; stats.py:402; */
"Status" = "Durum";
/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
"Documentation" = "Dokümantasyon";
/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
"Troubleshooting" = "Sorun giderme";
/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
"Report A Bug" = "Hata Bildir";
/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
"Privacy Policy" = "Gizlilik Politikası";
/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:937; EDMarketConnector.py:957; EDMarketConnector.py:1838; */
"Release Notes" = "Sürüm notları";
/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:949; EDMarketConnector.py:2265; prefs.py:241; */
"Settings" = "Ayarlar";
/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:950; */
"Exit" = "Çıkış";
/* 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ı";
/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */
"Logging in..." = "Giriş yapılıyor...";
/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:994; EDMarketConnector.py:1657; */
"Authentication successful" = "Kimlik doğrulama başarılı";
/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:1025; */
"You're not docked at a station!" = "Bir istasyona kenetli değilsiniz!";
/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:1033; */
"Station doesn't have anything!" = "İstasyon içeriği mevcut değil.";
/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */
"Station doesn't have a market!" = "İstasyon'da market bulunmuyor!";
/* 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 sorgusu iptal edildi: Cmdr adı bilinmiyor";
/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */
"CAPI query aborted: Game mode unknown" = "CAPI sorgusu iptal edildi: Oyun modu bilinmiyor";
/* 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 sorgusu iptal edildi: GameVersion bilinmiyor";
/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */
"CAPI query aborted: Current system unknown" = "CAPI sorgusu iptal edildi: Mevcut sistem bilinmiyor";
/* 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 sorgusu iptal edildi: Başka gemi çoklu-mürettebatı";
/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */
"CAPI query aborted: CQC (Arena) detected" = "CAPI sorgusu iptal edildi: CQC (Arena) tespit edildi";
/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */
"Fetching data..." = "Veri işleniyor...";
/* 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 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ı";
/* 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 commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
"CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi";
/* 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?!" = "Sen kimsin?!";
/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1252; stats.py:341; */
"Where are you?!" = "Neredesin?!";
/* 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?!" = "Ne uçuruyorsun?!";
/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */
"Frontier CAPI server error" = "Frontier CAPI sunucu hatası";
/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */
"CAPI: Refreshing access token..." = "CAPI: Erişim token yenileniyor...";
/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */
"Last updated at %H:%M:%S" = "Son güncelleme %H:%M:%S";
/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1462; */
"Fighter" = "Avcı";
/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1463; */
"Gunner" = "Nişancı";
/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1464; */
"Helm" = "Dümen";
/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1742; */
"cooldown {SS}s" = "bekleme süresi {SS}s";
/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1864; prefs.py:292; */
"OK" = "Tamam";
/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1936; */
"Shutting down..." = "Kapanıyor...";
/* 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: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
"Plugins" = "Eklentiler";
/* 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 Desteği Olmayan Eklentiler";
/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
"EDMC: Broken Plugins" = "EDMC: Bozuk Eklentiler";
/* 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." = "Etkin eklentilerinizden bir veya daha fazlası yüklenemedi. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Sorunun sebebi yanlış klasör yapısı olabilir. Load.py dosyası, plugins/PLUGIN_NAME/load.py altında bulunmalıdır.\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 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 Sağlayıcılarınızdan bir veya daha fazlası geçersizdi ve sıfırlandı:";
/* 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}'a ayarlanmıştı ancak {NEWPROV}'a sıfırlandı";
/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
"EDMC: Default Providers Reset" = "EDMC: Varsayılan Sağlayıcıların Sıfırlanması";
/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
"Awaiting Full CMDR Login" = "Tam CMDR Girişi Bekleniyor";
/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
"Journal directory already locked" = "Günlük dizini zaten kilitli";
/* 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." = "Yeni Günlük Dizini konumu zaten kilitli.{CR}Çözüm için Tekrar deneyebilir veya Yoksay'ı seçebilirsiniz.";
/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:230; */
"Retry" = "Tekrar Dene";
/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:234; */
"Ignore" = "Yoksay";
/* 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" = "Varsayılan";
/* 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" = "Otomatik";
/* 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" = "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" = "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='" = "Coriolis.io gemi ekipman tasarımları ile kullanılacak URL'yi ayarlayın. Link'in '/import?data=' ile bitmesi GEREKTİĞİNİ unutmayın.";
/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */
"Normal URL" = "Normal URL";
/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */
"Reset" = "Sıfırla";
/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */
"Beta URL" = "Beta 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" = "Beta/Normal Seçimi Geçersiz Kıl";
/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:156; */
"Invalid Coriolis override mode!" = "Geçersiz Coriolis geçersiz kılma modu!";
/* 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" = "Hata: EDDN'e bağlanılamıyor";
/* 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 Hatası: EDMC sürümü EDDN için çok eski. Lütfen güncelle.";
/* 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 Hatası: Doğrulama Başarısız Oldu (EDMC sürümü eski olabilir?). Günlüğe bakın";
/* 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 Hatası: {STATUS} durum kodu ile döndü.";
/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:2041; */
"Send station data to the Elite Dangerous Data Network" = "İstasyon verilerini Elite Dangerous Data Network'e gönderin";
/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:2052; */
"Send system and scan data to the Elite Dangerous Data Network" = "Sistem ve tarama verilerini Elite Dangerous Data Network'e gönderin";
/* 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" = "Kenetlenmeye kadar gönderimi ertele";
/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:2178; */
"EDDN journal handler disabled. See Log." = "EDDN günlük düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:2507; */
"Sending data to EDDN..." = "Veriler EDDN'e gönderiliyor...";
/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:319; */
"Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map kimlik bilgileri";
/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:341; */
"Commander Name" = "Cmdr ismi";
/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:350; inara.py:278; */
"API Key" = "API Anahtarı";
/* 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" = "Hiçbiri";
/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:516; */
"EDSM Handler disabled. See Log." = "EDSM düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
/* edsm.py: EDSM - Only Live data; In files: edsm.py:632; */
"EDSM only accepts Live galaxy data" = "EDSM sadece canlı galaxy verilerini kabul eder";
/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:916; edsm.py:1048; */
"Error: EDSM {MSG}" = "Hata: EDSM {MSG}";
/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */
"Error: Can't connect to EDSM" = "Hata: EDSM'e bağlanılamıyor";
/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:257; */
"Send flight log and Cmdr status to Inara" = "Uçuş günlüğünü ve Cmdr durumunu Inara'ya gönder";
/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */
"Inara credentials" = "Inara kimlik bilgileri";
/* 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 sadece canlı galaxy verilerini kabul eder";
/* inara.py: INARA support disabled via killswitch; In files: inara.py:395; */
"Inara disabled. See Log." = "Inara devre dışı. Kayıt geçmişini kontrol edin.";
/* 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: 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";
/* prefs.py: Settings > Output option; In files: prefs.py:341; */
"Market data in CSV format file" = "CSV formatında Market verileri";
/* prefs.py: Settings > Output option; In files: prefs.py:350; */
"Market data in Trade Dangerous format file" = "Trade Dangerous formatında Market verileri";
/* prefs.py: Settings > Output option; In files: prefs.py:360; */
"Ship loadout" = "Gemi Ekipmanları";
/* prefs.py: Settings > Output option; In files: prefs.py:370; */
"Automatically update on docking" = "Kenetlenme sırasında otomatik güncelle";
/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
"File location" = "Dosya konumu";
/* 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...";
/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:405; */
"Output" = "Kayıt";
/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
"E:D journal file location" = "E:D günlük dosya konumu";
/* 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 Fleet Carrier CAPI Queries" = "Filo Taşıyıcı CAPI Sorgularını Etkinleştir";
/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
"Hotkey" = "Kısayoltuşu";
/* 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";
/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:549; */
"Play sound" = "Ses çal";
/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:564; */
"Disable Automatic Application Updates Check when in-game" = "Otomatik Uygulama Güncellemelerini Oyun sırasında Devre Dışı Bırak";
/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:577; */
"Preferred websites" = "Tercih edilen web siteleri";
/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:588; */
"Shipyard" = "Tersane";
/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:600; */
"Use alternate URL method" = "Alternatif URL yöntemi kullan";
/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:653; */
"Log Level" = "Günlük Seviyesi";
/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:681; */
"Configuration" = "Yapılandırma";
/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:690; */
"Main UI privacy options" = "Ana Arayüz gizlilik seçenekleri";
/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:695; */
"Hide private group name in UI" = "Özel grup ismini arayüzde gizle";
/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:699; */
"Hide multi-crew captain name" = "Çoklu-mürettebat ismini gizle";
/* prefs.py: Preferences privacy tab title; In files: prefs.py:703; */
"Privacy" = "Gizlilik";
/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:716; */
"Normal text" = "Normal metin";
/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:718; */
"Highlighted text" = "Vurgulanan metin";
/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:727; */
"Language" = "Dil";
/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:737; */
"Theme" = "Tema";
/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:749; */
"Dark" = "Karanlık";
/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:756; */
"Transparent" = "Şeffaflık";
/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:802; */
"UI Scale Percentage" = "Arayüz ölçek yüzdesi";
/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:823; */
"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 Varsayılan değerdir. Değişikliklerin geçerli olması için {CR}Yeniden Başlatma Gereklidir{CR}!";
/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:833; */
"Main window transparency" = "Ana pencere şeffaflığı";
/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:853:856; */
"100 means fully opaque.{CR}Window is updated in real time" = "100 tamamen opak anlamına gelir.{CR}Pencere gerçek zamanlı olarak güncellenir";
/* prefs.py: Appearance option for Windows "minimize to system tray"; In files: prefs.py:885; */
"Minimize to system tray" = "Simge durumuna küçült";
/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:893; */
"Appearance" = "Görünüm";
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Eklentiler klasörü";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "İpucu: Bir eklentiyi, klasör adına{CR}'{EXT}' ekleyerek devre dışı bırakabilirsiniz";
/* prefs.py: Label on list of enabled plugins; In files: prefs.py:934; */
"Enabled Plugins" = "Etkin Eklentiler";
/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:954; */
"Plugins Without Python 3.x Support" = "Python 3.x Desteği Olmayan Eklentiler";
/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
"Information on migrating plugins" = "Eklentilerin taşınmasıyla ilgili bilgiler";
/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
"Broken Plugins" = "Bozuk Eklentiler";
/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
"Disabled Plugins" = "Devre Dışı Eklentiler";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Sistem Profilcisinde Hata oluştu";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Bakiye";
/* stats.py: Cmdr stats; In files: stats.py:59; */
"Loan" = "Borç";
/* stats.py: Top rank; In files: stats.py:63; */
"Elite" = "Elite";
/* stats.py: Top rank +1; In files: stats.py:64; */
"Elite I" = "Elite I";
/* stats.py: Top rank +2; In files: stats.py:65; */
"Elite II" = "Elite II";
/* stats.py: Top rank +3; In files: stats.py:66; */
"Elite III" = "Elite III";
/* stats.py: Top rank +4; In files: stats.py:67; */
"Elite IV" = "Elite IV";
/* stats.py: Top rank +5; In files: stats.py:68; */
"Elite V" = "Elite V";
/* stats.py: Ranking; In files: stats.py:74; */
"Combat" = "Combat";
/* stats.py: Ranking; In files: stats.py:75; */
"Trade" = "Trade";
/* stats.py: Ranking; In files: stats.py:76; */
"Explorer" = "Explorer";
/* stats.py: Ranking; In files: stats.py:77; */
"Mercenary" = "Mercenary";
/* stats.py: Ranking; In files: stats.py:78; */
"Exobiologist" = "Exobiologist";
/* stats.py: Ranking; In files: stats.py:79; */
"CQC" = "CQC";
/* stats.py: Ranking; In files: stats.py:80; */
"Federation" = "Federation";
/* stats.py: Ranking; In files: stats.py:81; */
"Empire" = "Empire";
/* stats.py: Ranking; In files: stats.py:82; */
"Powerplay" = "Powerplay";
/* stats.py: Combat rank; In files: stats.py:91; */
"Harmless" = "Harmless";
/* stats.py: Combat rank; In files: stats.py:92; */
"Mostly Harmless" = "Mostly Harmless";
/* stats.py: Combat rank; In files: stats.py:93; */
"Novice" = "Novice";
/* stats.py: Combat rank; In files: stats.py:94; */
"Competent" = "Competent";
/* stats.py: Combat rank; In files: stats.py:95; */
"Expert" = "Expert";
/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:96; stats.py:176; */
"Master" = "Master";
/* stats.py: Combat rank; In files: stats.py:97; */
"Dangerous" = "Dangerous";
/* stats.py: Combat rank; In files: stats.py:98; */
"Deadly" = "Deadly";
/* stats.py: Trade rank; In files: stats.py:101; */
"Penniless" = "Penniless";
/* stats.py: Trade rank; In files: stats.py:102; */
"Mostly Penniless" = "Mostly Penniless";
/* stats.py: Trade rank; In files: stats.py:103; */
"Peddler" = "Peddler";
/* stats.py: Trade rank; In files: stats.py:104; */
"Dealer" = "Dealer";
/* stats.py: Trade rank; In files: stats.py:105; */
"Merchant" = "Merchant";
/* stats.py: Trade rank; In files: stats.py:106; */
"Broker" = "Broker";
/* stats.py: Trade rank; In files: stats.py:107; */
"Entrepreneur" = "Entrepreneur";
/* stats.py: Trade rank; In files: stats.py:108; */
"Tycoon" = "Tycoon";
/* stats.py: Explorer rank; In files: stats.py:111; */
"Aimless" = "Aimless";
/* stats.py: Explorer rank; In files: stats.py:112; */
"Mostly Aimless" = "Mostly Aimless";
/* stats.py: Explorer rank; In files: stats.py:113; */
"Scout" = "Scout";
/* stats.py: Explorer rank; In files: stats.py:114; */
"Surveyor" = "Surveyor";
/* stats.py: Explorer rank; In files: stats.py:115; */
"Trailblazer" = "Trailblazer";
/* stats.py: Explorer rank; In files: stats.py:116; */
"Pathfinder" = "Pathfinder";
/* stats.py: Explorer rank; In files: stats.py:117; */
"Ranger" = "Ranger";
/* stats.py: Explorer rank; In files: stats.py:118; */
"Pioneer" = "Pioneer";
/* stats.py: Mercenary rank; In files: stats.py:122; */
"Defenceless" = "Defenceless";
/* stats.py: Mercenary rank; In files: stats.py:123; */
"Mostly Defenceless" = "Mostly Defenceless";
/* stats.py: Mercenary rank; In files: stats.py:124; */
"Rookie" = "Rookie";
/* stats.py: Mercenary rank; In files: stats.py:125; */
"Soldier" = "Soldier";
/* stats.py: Mercenary rank; In files: stats.py:126; stats.py:128; */
"Gunslinger" = "Gunslinger";
/* stats.py: Mercenary rank; In files: stats.py:127; */
"Warrior" = "Warrior";
/* stats.py: Mercenary rank; In files: stats.py:129; */
"Deadeye" = "Deadeye";
/* stats.py: Exobiologist rank; In files: stats.py:132; */
"Directionless" = "Directionless";
/* stats.py: Exobiologist rank; In files: stats.py:133; */
"Mostly Directionless" = "Mostly Directionless";
/* stats.py: Exobiologist rank; In files: stats.py:134; */
"Compiler" = "Compiler";
/* stats.py: Exobiologist rank; In files: stats.py:135; */
"Collector" = "Collector";
/* stats.py: Exobiologist rank; In files: stats.py:136; */
"Cataloguer" = "Cataloguer";
/* stats.py: Exobiologist rank; In files: stats.py:137; */
"Taxonomist" = "Taxonomist";
/* stats.py: Exobiologist rank; In files: stats.py:138; */
"Ecologist" = "Ecologist";
/* stats.py: Exobiologist rank; In files: stats.py:139; */
"Geneticist" = "Geneticist";
/* stats.py: CQC rank; In files: stats.py:142; */
"Helpless" = "Helpless";
/* stats.py: CQC rank; In files: stats.py:143; */
"Mostly Helpless" = "Mostly Helpless";
/* stats.py: CQC rank; In files: stats.py:144; */
"Amateur" = "Amateur";
/* stats.py: CQC rank; In files: stats.py:145; */
"Semi Professional" = "Semi Professional";
/* stats.py: CQC rank; In files: stats.py:146; */
"Professional" = "Professional";
/* stats.py: CQC rank; In files: stats.py:147; */
"Champion" = "Champion";
/* stats.py: CQC rank; In files: stats.py:148; */
"Hero" = "Hero";
/* stats.py: CQC rank; In files: stats.py:149; */
"Gladiator" = "Gladiator";
/* stats.py: Federation rank; In files: stats.py:155; */
"Recruit" = "Recruit";
/* stats.py: Federation rank; In files: stats.py:156; */
"Cadet" = "Cadet";
/* stats.py: Federation rank; In files: stats.py:157; */
"Midshipman" = "Midshipman";
/* stats.py: Federation rank; In files: stats.py:158; */
"Petty Officer" = "Petty Officer";
/* stats.py: Federation rank; In files: stats.py:159; */
"Chief Petty Officer" = "Chief Petty Officer";
/* stats.py: Federation rank; In files: stats.py:160; */
"Warrant Officer" = "Warrant Officer";
/* stats.py: Federation rank; In files: stats.py:161; */
"Ensign" = "Ensign";
/* stats.py: Federation rank; In files: stats.py:162; */
"Lieutenant" = "Lieutenant";
/* stats.py: Federation rank; In files: stats.py:163; */
"Lieutenant Commander" = "Lieutenant Commander";
/* stats.py: Federation rank; In files: stats.py:164; */
"Post Commander" = "Post Commander";
/* stats.py: Federation rank; In files: stats.py:165; */
"Post Captain" = "Post Captain";
/* stats.py: Federation rank; In files: stats.py:166; */
"Rear Admiral" = "Rear Admiral";
/* stats.py: Federation rank; In files: stats.py:167; */
"Vice Admiral" = "Vice Admiral";
/* stats.py: Federation rank; In files: stats.py:168; */
"Admiral" = "Admiral";
/* stats.py: Empire rank; In files: stats.py:174; */
"Outsider" = "Outsider";
/* stats.py: Empire rank; In files: stats.py:175; */
"Serf" = "Serf";
/* stats.py: Empire rank; In files: stats.py:177; */
"Squire" = "Squire";
/* stats.py: Empire rank; In files: stats.py:178; */
"Knight" = "Knight";
/* stats.py: Empire rank; In files: stats.py:179; */
"Lord" = "Lord";
/* stats.py: Empire rank; In files: stats.py:180; */
"Baron" = "Baron";
/* stats.py: Empire rank; In files: stats.py:181; */
"Viscount" = "Viscount";
/* stats.py: Empire rank; In files: stats.py:182; */
"Count" = "Count";
/* stats.py: Empire rank; In files: stats.py:183; */
"Earl" = "Earl";
/* stats.py: Empire rank; In files: stats.py:184; */
"Marquis" = "Marquis";
/* stats.py: Empire rank; In files: stats.py:185; */
"Duke" = "Duke";
/* stats.py: Empire rank; In files: stats.py:186; */
"Prince" = "Prince";
/* stats.py: Empire rank; In files: stats.py:187; */
"King" = "King";
/* stats.py: Power rank; In files: stats.py:193; */
"Rating 1" = "Reyting 1";
/* stats.py: Power rank; In files: stats.py:194; */
"Rating 2" = "Reyting 2";
/* stats.py: Power rank; In files: stats.py:195; */
"Rating 3" = "Reyting 3";
/* stats.py: Power rank; In files: stats.py:196; */
"Rating 4" = "Reyting 4";
/* stats.py: Power rank; In files: stats.py:197; */
"Rating 5" = "Reyting 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" = "Durum: Cmdr adınızı henüz bilmiyorum";
/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:323; */
"Status: No CAPI data yet" = "Durum: Henüz CAPI verisi yok";
/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:409; */
"Value" = "Değer";
/* stats.py: Status dialog title; In files: stats.py:418; */
"Ships" = "Gemiler";
/* 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}";
+278 -25
View File
@@ -1,9 +1,23 @@
/* 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 +36,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 +57,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 +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" = "Довідка";
@@ -70,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..." = "Зберегти необроблені дані...";
@@ -79,6 +105,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 +126,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 +153,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 Fleet Carrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
"CAPI: No Fleet Carrier data returned" = "CAPI: Немає даних корабля-носія";
/* 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 commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
"CAPI: No commander data returned" = "CAPI: Не отримано данних пілота";
@@ -124,6 +195,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 +231,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 +264,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 +312,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 +330,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 +348,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 +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..." = "Перегляд...";
@@ -244,21 +384,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 Fleet Carrier 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 +417,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" = "Конфіденційність";
@@ -325,8 +468,11 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "Сховище плагінів";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "Відкрити";
/* prefs.py: Label on button used to open the Plugin Folder; */
"Open Plugins Folder" = "Відкрити папку плагінів";
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
"Plugin Directory Location" = "Місцезнаходження папки плагінів";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Підказка: Ви можете відключити плагін через {CR} додання '{EXT}' до назви папки";
@@ -340,9 +486,15 @@
/* 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" = "Вимкнені плагіни";
/* prefs.py: Catch & Record Profiler Errors; */
"Error in System Profiler" = "Помилка у Профайлері Систем";
/* stats.py: Cmdr stats; In files: stats.py:58; */
"Balance" = "Баланс";
@@ -352,6 +504,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 +528,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 +618,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 +783,44 @@
/* 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 для завантаження останнього релізу?";
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
"Restart Required" = "Необхідно перезавантаження";
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
"A restart of EDMC is required. EDMC will now restart." = "Необхідно перезавантажити EDMC. EDMC буде зараз перезавантажений.";
/* 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}";
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
"Copy Inara SLEF" = "Копіювати Inara SLEF";
+3 -39
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,6 @@
/* 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:没有指挥官数据";
@@ -226,13 +214,13 @@
/* 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:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
/* 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:75; coriolis.py:121; coriolis.py:137; */
/* 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:76; coriolis.py:122; coriolis.py:138; */
/* 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; */
@@ -319,9 +307,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 +325,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 +337,9 @@
/* 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 在前台运行时生效";
@@ -448,9 +415,6 @@
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
"Plugins folder" = "插件文件夹";
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
"Open" = "打开";
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "提示:想要禁用某一项插件,{CR}你可以添加 {EXT} 到其文件夹名称中";
+77 -51
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):
@@ -655,6 +655,10 @@ Content of `state` (updated to the current journal entry):
| `ShipType` | `str` | Internal name for the current ship type |
| `HullValue` | `int` | Current ship value, excluding modules |
| `ModulesValue` | `int` | Value of the current ship's modules |
| `UnladenMass` | `float` | Unladen mass of current ship |
| `CargoCapacity` | `int` | Max cargo capacity of current ship |
| `MaxJumpRange` | `float` | Unladen jump range of current ship |
| `FuelCapacity` | `dict[str,float]` | Current max capacity of Main & Reserve tanks |
| `Rebuy` | `int` | Current ship's rebuy cost |
| `Modules` | `dict` | Currently fitted modules |
| `NavRoute` | `dict` | Last plotted multi-hop route[1] |
@@ -684,6 +688,7 @@ Content of `state` (updated to the current journal entry):
| `StationName`[3] | `Optional[str]` | Name of the station we're docked at, if applicable |
| `MarketID`[3] | `Optional[str]` | MarketID of the station we're docked at, if applicable |
| `StationType`[3] | `Optional[str]` | Type of the station we're docked at, if applicable |
| `Powerplay` | `dict` | `dict` of information on Powerplay
[1] - Contents of `NavRoute` not changed if a `NavRouteClear` event is seen,
but plugins will see the `NavRouteClear` event.
@@ -699,8 +704,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 +756,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 +765,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 +826,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.
@@ -832,6 +837,12 @@ the 'Body' name value.
`StationName`, `MarketID`, and `StationType` added to the `state` dictionary.
New in version 5.13.0:
`state` now has `Powerplay`, a `dict` including `Rank`, `Merits`, `Power`,
`TimePledged`, and `Votes`. `Votes` should only be populated if playing in
legacy mode, as it is no longer a concept in the current version of the game.
___
##### Synthetic Events
@@ -853,8 +864,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 +882,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 +904,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 +966,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 +1204,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 +1288,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 +1442,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.
+1 -1
View File
@@ -67,6 +67,6 @@ Please see the [Acknowledgements](https://github.com/EDCD/EDMarketConnector/wiki
License
-------
Copyright © 2015-2019 Jonathan Harris, 2020-2021 EDCD
Copyright © 2015-2019 Jonathan Harris, 2020-2024 EDCD
Licensed under the [GNU Public License (GPL)](http://www.gnu.org/licenses/gpl-2.0.html) version 2 or later.
+36 -21
View File
@@ -10,7 +10,6 @@ import shutil
import sys
import pathlib
from string import Template
from os.path import join, isdir
import py2exe
from config import (
appcmdname,
@@ -21,6 +20,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:
@@ -36,7 +36,7 @@ def iss_build(template_path: str, output_file: str) -> None:
new_file.write(newfile)
def system_check(dist_dir: str) -> str:
def system_check(dist_dir: pathlib.Path) -> str:
"""Check if the system is able to build."""
if sys.version_info < (3, 11):
sys.exit(f"Unexpected Python version {sys.version}")
@@ -54,17 +54,18 @@ def system_check(dist_dir: str) -> str:
print(f"Git short hash: {git_shorthash}")
if dist_dir and len(dist_dir) > 1 and isdir(dist_dir):
if dist_dir and pathlib.Path.is_dir(dist_dir):
shutil.rmtree(dist_dir)
return gitversion_file
def generate_data_files(
app_name: str, gitversion_file: str, plugins: list[str]
) -> list[tuple[str, list[str]]]:
) -> list[tuple[object, object]]:
"""Create the required datafiles to build."""
l10n_dir = "L10n"
fdevids_dir = "FDevIDs"
fdevids_dir = pathlib.Path("FDevIDs")
license_dir = pathlib.Path("docs/Licenses")
data_files = [
(
"",
@@ -76,12 +77,10 @@ def generate_data_files(
"ChangeLog.md",
"snd_good.wav",
"snd_bad.wav",
"modules.p", # TODO: Remove in 6.0
"resources/modules.json",
"resources/ships.json",
"ships.p", # TODO: Remove in 6.0
f"{app_name}.VisualElementsManifest.xml",
"modules.json",
"ships.json",
f"{app_name}.ico",
f"resources/{appcmdname}.ico",
"EDMarketConnector - TRACE.bat",
"EDMarketConnector - localserver-auth.bat",
"EDMarketConnector - reset-ui.bat",
@@ -89,23 +88,29 @@ def generate_data_files(
),
(
l10n_dir,
[join(l10n_dir, x) for x in os.listdir(l10n_dir) if x.endswith(".strings")],
[pathlib.Path(l10n_dir) / x for x in os.listdir(l10n_dir) if x.endswith(".strings")]
),
(
fdevids_dir,
[
join(fdevids_dir, "commodity.csv"),
join(fdevids_dir, "rare_commodity.csv"),
pathlib.Path(fdevids_dir / "commodity.csv"),
pathlib.Path(fdevids_dir / "rare_commodity.csv"),
],
),
("plugins", plugins),
]
# Add all files recursively from license directories
for root, dirs, files in os.walk(license_dir):
file_list = [os.path.join(root, f) for f in files]
dest_dir = os.path.join(license_dir, os.path.relpath(root, license_dir))
data_files.append((dest_dir, file_list))
return data_files
def build() -> None:
"""Build EDMarketConnector using Py2Exe."""
dist_dir: str = "dist.win32"
dist_dir: pathlib.Path = pathlib.Path("dist.win32")
gitversion_filename: str = system_check(dist_dir)
# Constants
@@ -124,7 +129,6 @@ def build() -> None:
"packages": [
"asyncio",
"multiprocessing",
"pkg_resources._vendor.platformdirs",
"sqlite3",
"util",
],
@@ -133,7 +137,6 @@ def build() -> None:
"distutils",
"_markerlib",
"optparse",
"PIL",
"simplejson",
"unittest",
"doctest",
@@ -144,7 +147,7 @@ def build() -> None:
}
# Function to generate DATA_FILES list
data_files: list[tuple[str, list[str]]] = generate_data_files(
data_files: list[tuple[object, object]] = generate_data_files(
appname, gitversion_filename, plugins
)
@@ -165,27 +168,38 @@ def build() -> None:
"script": "EDMarketConnector.py",
"icon_resources": [(0, f"{appname}.ico")],
"other_resources": [
(24, 1, pathlib.Path(f"{appname}.manifest").read_text(encoding="UTF8"))
(24, 1, pathlib.Path(f"resources/{appname}.manifest").read_text(encoding="UTF8"))
],
}
console_config: dict = {
"dest_base": appcmdname,
"script": "EDMC.py",
"icon_resources": [(0, f"resources/{appcmdname}.ico")],
"other_resources": [
(24, 1, pathlib.Path(f"{appcmdname}.manifest").read_text(encoding="UTF8"))
(24, 1, pathlib.Path(f"resources/{appcmdname}.manifest").read_text(encoding="UTF8"))
],
}
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,
)
except FileNotFoundError:
except FileNotFoundError as err:
print(err)
sys.exit(
"Build Failed due to Missing Files! Have you set up your submodules? \n"
"https://github.com/EDCD/EDMarketConnector/wiki/Running-from-source"
@@ -199,4 +213,5 @@ def build() -> None:
if __name__ == "__main__":
check_for_fdev_updates(local=True)
build()
+12 -9
View File
@@ -17,11 +17,11 @@ import json
import os
import pathlib
import sys
from os.path import isfile
from traceback import print_exc
import companion
import outfitting
from config import config
from edmc_data import companion_category_map, ship_name_map
@@ -34,7 +34,7 @@ def __make_backup(file_name: pathlib.Path, suffix: str = '.bak') -> None:
"""
backup_name = file_name.parent / (file_name.name + suffix)
if isfile(backup_name):
if pathlib.Path.is_file(backup_name):
os.unlink(backup_name)
os.rename(file_name, backup_name)
@@ -50,11 +50,14 @@ def addcommodities(data) -> None: # noqa: CCR001
if not data['lastStarport'].get('commodities'):
return
commodityfile = pathlib.Path('FDevIDs/commodity.csv')
try:
commodityfile = config.app_dir_path / 'FDevIDs' / 'commodity.csv'
except FileNotFoundError:
commodityfile = pathlib.Path('FDevIDs/commodity.csv')
commodities = {}
# slurp existing
if isfile(commodityfile):
if pathlib.Path.is_file(commodityfile):
with open(commodityfile) as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
@@ -82,7 +85,7 @@ def addcommodities(data) -> None: # noqa: CCR001
if len(commodities) <= size_pre:
return
if isfile(commodityfile):
if pathlib.Path.is_file(commodityfile):
__make_backup(commodityfile)
with open(commodityfile, 'w', newline='\n') as csvfile:
@@ -105,7 +108,7 @@ def addmodules(data): # noqa: C901, CCR001
fields = ('id', 'symbol', 'category', 'name', 'mount', 'guidance', 'ship', 'class', 'rating', 'entitlement')
# slurp existing
if isfile(outfile):
if pathlib.Path.is_file(outfile):
with open(outfile) as csvfile:
reader = csv.DictReader(csvfile, restval='')
for row in reader:
@@ -143,7 +146,7 @@ def addmodules(data): # noqa: C901, CCR001
if not len(modules) > size_pre:
return
if isfile(outfile):
if pathlib.Path.is_file(outfile):
__make_backup(outfile)
with open(outfile, 'w', newline='\n') as csvfile:
@@ -166,7 +169,7 @@ def addships(data) -> None: # noqa: CCR001
fields = ('id', 'symbol', 'name')
# slurp existing
if isfile(shipfile):
if pathlib.Path.is_file(shipfile):
with open(shipfile) as csvfile:
reader = csv.DictReader(csvfile, restval='')
for row in reader:
@@ -196,7 +199,7 @@ def addships(data) -> None: # noqa: CCR001
if not len(ships) > size_pre:
return
if isfile(shipfile):
if pathlib.Path.is_file(shipfile):
__make_backup(shipfile)
with open(shipfile, 'w', newline='\n') as csvfile:
+2 -2
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import time
from os.path import join
from pathlib import Path
from config import config
from edmc_data import commodity_bracketmap as bracketmap
@@ -29,7 +29,7 @@ def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None:
filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime))
filename_kind = 'csv'
filename = f'{filename_system}.{filename_starport}.{filename_time}.{filename_kind}'
filename = join(config.get_str('outdir'), filename)
filename = Path(config.get_str('outdir')) / filename
if kind == COMMODITY_CSV:
sep = ';' # BUG: for fixing later after cleanup
+23 -23
View File
@@ -27,8 +27,9 @@ import tkinter as tk
import urllib.parse
import webbrowser
from email.utils import parsedate
from pathlib import Path
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 +38,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 +224,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 +243,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 +259,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 +269,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 +294,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 +429,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 +472,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 +505,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 +514,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 +841,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 +874,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 +1064,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 +1075,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,
@@ -1136,7 +1136,7 @@ class Session:
def dump_capi_data(self, data: CAPIData) -> None:
"""Dump CAPI data to file for examination."""
if os.path.isdir('dump'):
if Path('dump').is_dir():
file_name: str = ""
if data.source_endpoint == self.FRONTIER_CAPI_PATH_FLEETCARRIER:
file_name += f"FleetCarrier.{data['name']['callsign']}"
@@ -1204,10 +1204,10 @@ def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully
if not commodity_map:
# Lazily populate
for f in ('commodity.csv', 'rare_commodity.csv'):
if not os.path.isfile(config.respath_path / 'FDevIDs/' / f):
if not (config.app_dir_path / 'FDevIDs' / f).is_file():
logger.warning(f'FDevID file {f} not found! Generating output without these commodity name rewrites.')
continue
with open(config.respath_path / 'FDevIDs' / f, 'r') as csvfile:
with open(config.app_dir_path / 'FDevIDs' / f, 'r') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
@@ -1328,7 +1328,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)}')
+51 -27
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,8 @@ __all__ = [
'user_agent',
'appversion_nobuild',
'AbstractConfig',
'config'
'config',
'get_update_feed',
]
import abc
@@ -41,7 +40,6 @@ import pathlib
import re
import subprocess
import sys
import traceback
import warnings
from abc import abstractmethod
from typing import Any, Callable, Type, TypeVar
@@ -54,12 +52,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.0'
_static_appversion = '5.12.2'
_cached_version: semantic_version.Version | None = None
copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD'
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] = []
@@ -81,15 +78,15 @@ else:
_T = TypeVar('_T')
def git_shorthash_from_head() -> str:
def git_shorthash_from_head() -> str | None:
"""
Determine short hash for current git HEAD.
Includes `.DIRTY` if any changes have been made from HEAD
:return: str - None if we couldn't determine the short hash.
:return: str | None: None if we couldn't determine the short hash.
"""
shorthash: str = None # type: ignore
shorthash: str | None = None
try:
git_cmd = subprocess.Popen(
@@ -99,14 +96,14 @@ def git_shorthash_from_head() -> str:
)
out, err = git_cmd.communicate()
except subprocess.CalledProcessError as e:
except (subprocess.CalledProcessError, FileNotFoundError) as e:
logger.info(f"Couldn't run git command for short hash: {e!r}")
else:
shorthash = out.decode().rstrip('\n')
if re.match(r'^[0-9a-f]{7,}$', shorthash) is None:
logger.error(f"'{shorthash}' doesn't look like a valid git short hash, forcing to None")
shorthash = None # type: ignore
shorthash = None
if shorthash is not None:
with contextlib.suppress(Exception):
@@ -134,13 +131,19 @@ def appversion() -> semantic_version.Version:
# Running frozen, so we should have a .gitversion file
# Yes, .parent because if frozen we're inside library.zip
with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, encoding='utf-8') as gitv:
shorthash = gitv.read()
shorthash: str | None = gitv.read()
else:
# Running from source
# Running from source. Use git rev-parse --short HEAD
# or fall back to .gitversion file if it exists.
# This is also required for the Flatpak
shorthash = git_shorthash_from_head()
if shorthash is None:
shorthash = 'UNKNOWN'
if pathlib.Path(sys.path[0] + "/" + GITVERSION_FILE).exists():
with open(pathlib.Path(sys.path[0] + "/" + GITVERSION_FILE), encoding='utf-8') as gitv:
shorthash = gitv.read()
else:
shorthash = 'UNKNOWN'
_cached_version = semantic_version.Version(f'{_static_appversion}+{shorthash}')
return _cached_version
@@ -186,6 +189,7 @@ class AbstractConfig(abc.ABC):
app_dir_path: pathlib.Path
plugin_dir_path: pathlib.Path
default_plugin_dir_path: pathlib.Path
internal_plugin_dir_path: pathlib.Path
respath_path: pathlib.Path
home_path: pathlib.Path
@@ -276,6 +280,11 @@ class AbstractConfig(abc.ABC):
"""Return a string version of plugin_dir."""
return str(self.plugin_dir_path)
@property
def default_plugin_dir(self) -> str:
"""Return a string version of plugin_dir."""
return str(self.default_plugin_dir_path)
@property
def internal_plugin_dir(self) -> str:
"""Return a string version of internal_plugin_dir."""
@@ -324,8 +333,8 @@ class AbstractConfig(abc.ABC):
:raises OSError: On Windows, if a Registry error occurs.
:return: The data or the default.
"""
warnings.warn(DeprecationWarning('get is Deprecated. use the specific getter for your type'))
logger.debug('Attempt to use Deprecated get() method\n' + ''.join(traceback.format_stack()))
# DEPRECATED: Migrate to specific type getters. Will remove in 6.0 or later.
warnings.warn('get is Deprecated. use the specific getter for your type', DeprecationWarning, stacklevel=2)
if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None:
return a_list
@@ -383,8 +392,8 @@ class AbstractConfig(abc.ABC):
See get_int for its replacement.
:raises OSError: On Windows, if a Registry error occurs.
"""
warnings.warn(DeprecationWarning('getint is Deprecated. Use get_int instead'))
logger.debug('Attempt to use Deprecated getint() method\n' + ''.join(traceback.format_stack()))
# DEPRECATED: Migrate to get_int. Will remove in 6.0 or later.
warnings.warn('getint is Deprecated. Use get_int instead', DeprecationWarning, stacklevel=2)
return self.get_int(key, default=default)
@@ -441,17 +450,19 @@ class AbstractConfig(abc.ABC):
"""Close this config and release any associated resources."""
raise NotImplementedError
# DEPRECATED: Password system doesn't do anything. Will remove in 6.0 or later.
def get_password(self, account: str) -> None:
"""Legacy password retrieval."""
warnings.warn("password subsystem is no longer supported", DeprecationWarning)
warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2)
def set_password(self, account: str, password: str) -> None:
"""Legacy password setting."""
warnings.warn("password subsystem is no longer supported", DeprecationWarning)
warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2)
def delete_password(self, account: str) -> None:
"""Legacy password deletion."""
warnings.warn("password subsystem is no longer supported", DeprecationWarning)
warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2)
# End Dep Zone
def get_config(*args, **kwargs) -> AbstractConfig:
@@ -462,10 +473,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)
@@ -478,3 +485,20 @@ 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'
# DEPRECATED: Migrate to get_update_feed(). Will remove in 6.0 or later.
def __getattr__(name: str):
if name == 'update_feed':
warnings.warn('update_feed is deprecated, and will be removed in 6.0 or later. '
'Please migrate to get_update_feed()', DeprecationWarning, stacklevel=2)
return get_update_feed()
raise AttributeError(name=name)
-191
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
+9 -3
View File
@@ -5,6 +5,8 @@ Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
from __future__ import annotations
import os
import pathlib
import sys
@@ -29,9 +31,7 @@ class LinuxConfig(AbstractConfig):
self.app_dir_path = xdg_data_home / appname
self.app_dir_path.mkdir(exist_ok=True, parents=True)
self.plugin_dir_path = self.app_dir_path / 'plugins'
self.plugin_dir_path.mkdir(exist_ok=True)
self.default_plugin_dir_path = self.app_dir_path / 'plugins'
self.respath_path = pathlib.Path(__file__).parent.parent
self.internal_plugin_dir_path = self.respath_path / 'plugins'
@@ -60,6 +60,12 @@ class LinuxConfig(AbstractConfig):
self.config.add_section(self.SECTION)
if (plugdir_str := self.get_str('plugin_dir')) is None or not pathlib.Path(plugdir_str).is_dir():
self.set("plugin_dir", str(self.default_plugin_dir_path))
plugdir_str = self.default_plugin_dir
self.plugin_dir_path = pathlib.Path(plugdir_str)
self.plugin_dir_path.mkdir(exist_ok=True)
if (outdir := self.get_str('outdir')) is None or not pathlib.Path(outdir).is_dir():
self.set('outdir', self.home)
+27 -39
View File
@@ -7,41 +7,23 @@ See LICENSE file.
"""
from __future__ import annotations
import ctypes
import functools
import pathlib
import sys
import uuid
import winreg
from ctypes.wintypes import DWORD, HANDLE
from typing import Literal
from config import AbstractConfig, applongname, appname, logger
from win32comext.shell import shell
assert sys.platform == 'win32'
REG_RESERVED_ALWAYS_ZERO = 0
# This is the only way to do this from python without external deps (which do this anyway).
FOLDERID_Documents = uuid.UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}')
FOLDERID_LocalAppData = uuid.UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}')
FOLDERID_Profile = uuid.UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}')
FOLDERID_SavedGames = uuid.UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}')
SHGetKnownFolderPath = ctypes.windll.shell32.SHGetKnownFolderPath
SHGetKnownFolderPath.argtypes = [ctypes.c_char_p, DWORD, HANDLE, ctypes.POINTER(ctypes.c_wchar_p)]
CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree
CoTaskMemFree.argtypes = [ctypes.c_void_p]
def known_folder_path(guid: uuid.UUID) -> str | None:
"""Look up a Windows GUID to actual folder path name."""
buf = ctypes.c_wchar_p()
if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)):
return None
retval = buf.value # copy data
CoTaskMemFree(buf) # and free original
return retval
return shell.SHGetKnownFolderPath(guid, 0, 0)
class WinConfig(AbstractConfig):
@@ -49,24 +31,6 @@ class WinConfig(AbstractConfig):
def __init__(self) -> None:
super().__init__()
self.app_dir_path = pathlib.Path(known_folder_path(FOLDERID_LocalAppData)) / appname # type: ignore
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)
if getattr(sys, 'frozen', False):
self.respath_path = pathlib.Path(sys.executable).parent
self.internal_plugin_dir_path = self.respath_path / 'plugins'
else:
self.respath_path = pathlib.Path(__file__).parent.parent
self.internal_plugin_dir_path = self.respath_path / 'plugins'
self.home_path = pathlib.Path.home()
journal_dir_path = pathlib.Path(
known_folder_path(FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806
create_key_defaults = functools.partial(
@@ -82,9 +46,33 @@ class WinConfig(AbstractConfig):
logger.exception('Could not create required registry keys')
raise
if local_appdata := known_folder_path(shell.FOLDERID_LocalAppData):
self.app_dir_path = pathlib.Path(local_appdata) / appname
self.app_dir_path.mkdir(exist_ok=True)
self.default_plugin_dir_path = self.app_dir_path / 'plugins'
if (plugdir_str := self.get_str('plugin_dir')) is None or not pathlib.Path(plugdir_str).is_dir():
self.set("plugin_dir", str(self.default_plugin_dir_path))
plugdir_str = self.default_plugin_dir
self.plugin_dir_path = pathlib.Path(plugdir_str)
self.plugin_dir_path.mkdir(exist_ok=True)
if getattr(sys, 'frozen', False):
self.respath_path = pathlib.Path(sys.executable).parent
self.internal_plugin_dir_path = self.respath_path / 'plugins'
else:
self.respath_path = pathlib.Path(__file__).parent.parent
self.internal_plugin_dir_path = self.respath_path / 'plugins'
self.home_path = pathlib.Path.home()
journal_dir_path = pathlib.Path(
known_folder_path(shell.FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
self.identifier = applongname
if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir():
docs = known_folder_path(FOLDERID_Documents)
docs = known_folder_path(shell.FOLDERID_Documents)
self.set("outdir", docs if docs is not None else self.home)
def __get_regentry(self, key: str) -> None | list | str | int:
+6 -10
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
@@ -52,12 +51,13 @@ if __name__ == "__main__":
for m in list(data['Ships'].values()):
name = coriolis_ship_map.get(m['properties']['name'], str(m['properties']['name']))
assert name in reverse_ship_map, name
ships[name] = {'hullMass': m['properties']['hullMass']}
ships[name] = {'hullMass': m['properties']['hullMass'],
'reserveFuelCapacity': m['properties']['reserveFuelCapacity']}
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
with open("resources/ships.json", "w") as ships_file:
ships = {k: ships[k] for k in sorted(ships)}
with open("ships.json", "w") as ships_file:
json.dump(ships, ships_file, indent=4)
# Module masses
@@ -87,10 +87,6 @@ if __name__ == "__main__":
add(modules, 'int_stellarbodydiscoveryscanner_intermediate', {'mass': 2})
add(modules, 'int_stellarbodydiscoveryscanner_advanced', {'mass': 2})
# Missing
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
with open("resources/modules.json", "w") as modules_file:
modules = {k: modules[k] for k in sorted(modules)}
with open("modules.json", "w") as modules_file:
json.dump(modules, modules_file, indent=4)
+9 -8
View File
@@ -12,7 +12,7 @@ import sys
import time
import tkinter as tk
from calendar import timegm
from os.path import getsize, isdir, isfile, join
from pathlib import Path
from typing import Any, cast
from watchdog.observers.api import BaseObserver
from config import config
@@ -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):
@@ -57,7 +57,7 @@ class Dashboard(FileSystemEventHandler):
logdir = config.get_str('journaldir', default=config.default_journal_dir)
logdir = logdir or config.default_journal_dir
if not isdir(logdir):
if not Path.is_dir(Path(logdir)):
logger.info(f"No logdir, or it isn't a directory: {logdir=}")
self.stop()
return False
@@ -87,7 +87,7 @@ class Dashboard(FileSystemEventHandler):
if not self.observed and sys.platform == 'win32':
logger.debug('Starting observer...')
self.observed = cast(BaseObserver, self.observer).schedule(self, self.currentdir)
self.observed = cast(BaseObserver, self.observer).schedule(self, self.currentdir) # type: ignore
logger.debug('Done')
logger.info(f'{(sys.platform != "win32") and "Polling" or "Monitoring"} Dashboard "{self.currentdir}"')
@@ -160,11 +160,12 @@ 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.
"""
if event.is_directory or (isfile(event.src_path) and getsize(event.src_path)):
modpath = Path(event.src_path)
if event.is_directory or (modpath.is_file() and modpath.stat().st_size):
# Can get on_modified events when the file is emptied
self.process(event.src_path if not event.is_directory else None)
@@ -177,7 +178,7 @@ class Dashboard(FileSystemEventHandler):
if config.shutting_down:
return
try:
status_json_path = join(self.currentdir, 'Status.json')
status_json_path = Path(self.currentdir) / 'Status.json'
with open(status_json_path, 'rb') as h:
data = h.read().strip()
if data: # Can be empty if polling while the file is being re-written
+3 -5
View File
@@ -4,21 +4,19 @@ from __future__ import annotations
import gzip
import json
import pathlib
import tempfile
import threading
import zlib
from http import server
from typing import Any, Callable, Literal
from urllib.parse import parse_qs
from config import appname
import config
from EDMCLogging import get_main_logger
logger = get_main_logger()
output_lock = threading.Lock()
output_data_path = pathlib.Path(tempfile.gettempdir()) / f'{appname}' / 'http_debug'
SAFE_TRANSLATE = str.maketrans({x: '_' for x in "!@#$%^&*()./\\\r\n[]-+='\";:?<>,~`"})
output_data_path = pathlib.Path(config.app_dir_path / 'logs' / 'http_debug')
SAFE_TRANSLATE = str.maketrans(dict.fromkeys("!@#$%^&*()./\\\r\n[]-+='\";:?<>,~`", '_'))
class LoggingHandler(server.BaseHTTPRequestHandler):
-28
View File
@@ -1,28 +0,0 @@
Copyright (c) .NET Foundation and contributors.
This software is released under the Microsoft Reciprocal License (MS-RL) (the "License"); you may not use the software except in compliance with the License.
The text of the Microsoft Reciprocal License (MS-RL) can be found online at:
http://opensource.org/licenses/ms-rl
Microsoft Reciprocal License (MS-RL)
This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
3. Conditions and Limitations
(A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. You may license other files that are entirely your own work and do not contain code from the software under any terms you choose.
(B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
(D) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
(E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
(F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
@@ -0,0 +1,30 @@
The Python Imaging Library (PIL) is
Copyright © 1997-2011 by Secret Labs AB
Copyright © 1995-2011 by Fredrik Lundh and contributors
Pillow is the friendly PIL fork. It is
Copyright © 2010-2024 by Jeffrey A. Clark and contributors
Like PIL, Pillow is licensed under the open source HPND License:
By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply
with the following terms and conditions:
Permission to use, copy, modify and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that
both that copyright notice and this permission notice appear in supporting
documentation, and that the name of Secret Labs AB or the author not be
used in advertising or publicity pertaining to distribution of the software
without specific, written prior permission.
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL,
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the psutil authors nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,396 @@
Attribution 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution 4.0 International Public License ("Public License"). To the
extent this Public License may be interpreted as a contract, You are
granted the Licensed Rights in consideration of Your acceptance of
these terms and conditions, and the Licensor grants You such rights in
consideration of benefits the Licensor receives from making the
Licensed Material available under these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
d. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
e. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
f. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
4. If You Share Adapted Material You produce, the Adapter's
License You apply must not prevent recipients of the Adapted
Material from complying with this Public License.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.
+14 -8
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.";
+4 -3
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.
+7 -7
View File
@@ -3,10 +3,10 @@
"""Plugin that tests that modules we bundle for plugins are present and working."""
import logging
import os
import shutil
import sqlite3
import zipfile
from pathlib import Path
import semantic_version
from SubA import SubA
@@ -14,7 +14,7 @@ from SubA import SubA
from config import appname, appversion, config
# This could also be returned from plugin_start3()
plugin_name = os.path.basename(os.path.dirname(__file__))
plugin_name = Path(__file__).resolve().parent.name
# Logger per found plugin, so the folder name is included in
# the logging format.
@@ -49,17 +49,17 @@ class PluginTest:
def __init__(self, directory: str):
logger.debug(f'directory = "{directory}')
dbfile = os.path.join(directory, this.DBFILE)
dbfile = Path(directory) / this.DBFILE
# Test 'import zipfile'
with zipfile.ZipFile(dbfile + '.zip', 'w') as zip:
if os.path.exists(dbfile):
with zipfile.ZipFile(str(dbfile) + '.zip', 'w') as zip:
if dbfile.exists():
zip.write(dbfile)
zip.close()
# Testing 'import shutil'
if os.path.exists(dbfile):
shutil.copyfile(dbfile, dbfile + '.bak')
if dbfile.exists():
shutil.copyfile(dbfile, str(dbfile) + '.bak')
# Testing 'import sqlite3'
self.sqlconn = sqlite3.connect(dbfile)
-252
View File
@@ -1,252 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>E:D Market Connector</title>
<link>https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml</link>
<description>Most recent changes with links to updates.</description>
<!-- Important: Mac must be listed before Windows since Sparkle < 1.18.1 on OSX doesn't check sparkle:os -->
<item>
<title>Release 3.44</title>
<description>
<![CDATA[
<style>h2 { font-size: 105%; }</style>
<h2>Release 3.44</h2>
<p><b>CHANGE OF MAINTAINER</b></p>
<p>Due to a lack of time to give the project the attention it needs Marginal has handed over ownership of the EDMarketConnector GitHub repository to the EDCD (Elite Dangerous Community Developers) organisation.</p>
<p>Initially Athanasius will now be responsible for maintaining the code, including addressing any Pull Requests and Issues, and making releases. <b><u>Unfortunately he has no access to hardware running MacOS so can't easily generate builds for that platform</u> or test them. So for the time being releases will be for Windows 10 only.</b> MacOS users are advised to look into running from source (see the github README).</p>
<p>Going forwards the intention is to move to the python 3.7 code as soon as possible. To facilitate this there will be one more python 2.7 release in addition to this one, with the main aim of that being to add code to alert the user about any plugins they use that have apparently not been updated to run under python 3.7.</p>
<p>See the project GitHub repository's <a href="https://github.com/EDCD/EDMarketConnector/blob/main/README.md">README.md</a> for further information.</p>
<ul>
<li>Version increased to 3.4.4.0 / 3.44.</li>
<li>URL the application checks for updates changed to point to github,</li>
</ul>
]]>
</description>
<enclosure
url="https://github.com/EDCD/EDMarketConnector/releases/download/rel-344/EDMarketConnector_mac_344.zip"
sparkle:os="macos"
sparkle:version="3.4.4.0"
length="6058091"
type="application/octet-stream"
/>
</item>
<!-- Windows -->
<item>
<title>Release 4.2.3</title>
<description>
<![CDATA[
<style>body { font-family:"Segoe UI","Tahoma"; font-size: 75%; } h2 { font-family:"Segoe UI","Tahoma"; font-size: 105%; }</style>
<h2>Release 4.2.3</h2>
<p>This release mostly addresses an issue when Frontier Authorisation gets stuck
on 'Logging in...' despite completing the authorisation on the Frontier
website.</p>
<ul>
<li>
<p>Allow <code>edmc...</code> argument to EDMarketConnector.exe. This should only be
necessary when something has prevented your web browser from invoking the
<code>edmc</code> protocol via DDE.</p>
<p>If you were encountering the 'Logging in...' issue and still do with this
release then please try running the application via the new
<code>EDMarketConnector - localserver-auth.bat</code> file in the installation
directory.</p>
<p>This simply runs EDMarketConnector.exe with the
<code>--force-localserver-for-auth</code> command-line argument. This forces the code
to setup and use a webserver on a random port on localhost for the
Frontier Authorisation callback, the same way it already works on
non-Windows platforms.</p>
</li>
<li>
<p>Add Korean translation to both the application and the installer.</p>
</li>
</ul>
<h2>Release 4.2.2</h2>
<p>This release contains a minor bug-fix, actually properly checking a station's
ships list before operating on it.</p>
<ul>
<li>Check that <code>ships['shipuard_list']</code> is a <code>dict</code> before trying to use
<code>.values()</code> on it. This fixes the issue with seeing <code>list object has no attribute values</code> in the application status line.</li>
</ul>
<h2>Release 4.2.1</h2>
<p>This is a bug-fix release.</p>
<ul>
<li>
<p>Updated translations. Thanks once again to all those contributing as per
<a href="https://github.com/EDCD/EDMarketConnector/wiki/Translations">Translations</a>.</p>
</li>
<li>
<p>PLUGINS.md: Clarify when <code>CargoJSON</code> is populated.</p>
</li>
<li>
<p>macOS: <code>pip install -r requirements.txt</code> will now include <code>pyobjc</code> so that
running this application works at all. Check the updated <a href="https://github.com/EDCD/EDMarketConnector/wiki/Running-from-source">Running from
source</a>
for some advice if attempting to run on macOS.</p>
</li>
<li>
<p>JournalLock: Handle when the Journal directory isn't set at all, rather than
erroring. Fixes <a href="https://github.com/EDCD/EDMarketConnector/issues/910">#910 - Not launching (Linux)</a>.</p>
</li>
<li>
<p>Extra logging added to track down cause of <a href="https://github.com/EDCD/EDMarketConnector/issues/909">#909 - Authentication not possible (PC)</a>
. The debug log file might now indicate what's wrong, or we might need
you to run</p>
<pre><code>"c:\Program Files (x86)\EDMarketConnector/EDMarketConnector.exe" --trace
</code></pre>
<p>in order to increase the log level and gather some extra information.
Caution is advised if sharing a <code>--trace</code> log file as it will now contain
some of the actual auth data returned from Frontier.</p>
</li>
<li>
<p>Ensure that 'Save Raw Data' will work. Fixes <a href="https://github.com/EDCD/EDMarketConnector/issues/908">#908 - Raw export of CAPI data broken</a>.</p>
</li>
<li>
<p>Prevent EDDN plugin from erroring when we determine if the commander has
Horizons. Fixes <a href="https://github.com/EDCD/EDMarketConnector/issues/907">#907 - Modules is a list not a dict on damaged stations</a></p>
</li>
</ul>
<h2>Release 4.2.0</h2>
<p><em>This release increases the Minor version due to the major change in how
multiple-instance checking is done.</em></p>
<ul>
<li>
<p>Adds Steam and Epic to the list of "audiences" in the Frontier Auth callout
so that you can authorise using those accounts, rather than their associated
Frontier Account details.</p>
</li>
<li>
<p>New status message "CAPI: No commander data returned" if a <code>/profile</code>
request has no commander in the returned data. This can happen if you
literally haven't yet created a Commander on the account. Previously you'd
get a confusing <code>'commander'</code> message shown.</p>
</li>
<li>
<p>Changes the "is there another process already running?" check to be based on
a lockfile in the configured Journals directory. The name of this file is
<code>edmc-journal-lock.txt</code> and upon successful locking it will contain text
like:</p>
<pre><code>Path: &lt;configured path to your Journals&gt;
PID: &lt;process ID of the application&gt;
</code></pre>
<p>The lock will be released and applied to the new directory if you change it
via Settings &gt; Configuration. If the new location is already locked you'll
get a 'Retry/Ignore?' pop-up.</p>
<p>For most users things will operate no differently, although note that the
multiple instance check does now apply to platforms other than Windows.</p>
<p>For anyone wanting to run multiple instances of the program this is now
possible via:</p>
<p><code>runas /user:&lt;USER&gt; "\"c:\Program Files (x86)\EDMarketConnector\EDMarketConnector.exe\" --force-localserver-for-auth"</code></p>
<p>If anything has messed with the backslash characters there then know that you
need to have " (double-quote) around the entire command (path to program .exe
<em>and</em> any extra arguments), and as a result need to place a backslash before
any double-quote characters in the command (such as around the space-including
path to the program).</p>
<p>I've verified it renders correctly <a href="https://github.com/EDCD/EDMarketConnector/blob/Release/4.2.0/ChangeLog.md">on GitHub</a>.</p>
<p>The old check was based solely on there being a window present with the title
we expect. This prevented using <code>runas /user:SOMEUSER ...</code> to run a second
copy of the application, as the resulting window would still be within the
same desktop environment and thus be found in the check.</p>
<p>The new method does assume that the Journals directory is writable by the
user we're running as. This might not be true in the case of sharing the
file system to another host in a read-only manner. If we fail to open the
lock file read-write then the application aborts the checks and will simply
continue running as normal.</p>
<p>Note that any single instance of EDMarketConnector.exe will still only monitor
and act upon the <em>latest</em> Journal file in the configured location. If you run
Elite Dangerous for another Commander then the application will want to start
monitoring that separate Commander. See <a href="https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#i-run-two-instances-of-ed-simultaneously-but-i-cant-run-two-instances-of-edmc%3E">wiki:Troubleshooting#i-run-two-instances-of-ed-simultaneously-but-i-cant-run-two-instances-of-edmc</a>
which will be updated when this change is in a full release.</p>
</li>
<li>
<p>Adds the command-line argument <code>--force-localserver-for-auth</code>. This forces
using a local webserver for the Frontier Auth callback. This should be used
when running multiple instances of the application <strong>for all instances</strong>
else there's no guarantee of the <code>edmc://</code> protocol callback reaching the
correct process and Frontier Auth will fail.</p>
</li>
<li>
<p>Adds the command-line argument <code>--suppress-dupe-process-popup</code> to exit
without showing the warning popup in the case that EDMarketConnector found
another process already running.</p>
<p>This can be useful if wanting to blindly run both EDMC and the game from a
batch file or similar.</p>
</li>
</ul>
<h2>Release 4.1.6</h2>
<p>We might have finally found the cause of the application hangs during shutdown.
Note that this became easier to track down due to the downtime
for migration of <a href="http://www.edsm.net" rel="nofollow">www.edsm.net</a> around 2021-01-11. Before these fixes EDSM's
API not being available would cause an EDMC hang on shutdown.</p>
<ul>
<li>
<p>We've applied extra paranoia to some of the application shutdown code to
ensure we're not still trying to handle journal events during this sequence.</p>
<p>We also re-ordered the shutdown sequence, which might help avoid the shutdown
hang.</p>
<p>If you encounter a shutdown hang then please add a comment and log files to
<a href="https://github.com/EDCD/EDMarketConnector/issues/678">Application can leave a zombie process on shutdown #678</a>
to help us track down the cause and fix it.</p>
</li>
<li>
<p>We now avoid making Tk event_generate() calls whilst the appliction is
shutting down.</p>
</li>
<li>
<p>Plugins should actively avoid making any sort of Tk event_generate() call
during application shutdown.</p>
<p>This means using <code>if not config.shutting_down:</code> to gate any code in worker
threads that might attempt this. Also, be sure you're not attempting such
in your <code>plugin_stop()</code> function.</p>
<p>See plugins/edsm.py and plugins/inara.py for example of the usage.</p>
</li>
<li>
<p>Any use of <code>plug.show_error()</code> won't actually change the UI status line
during shutdown, but the text you tried to show will be logged instead.</p>
</li>
<li>
<p>Cargo tracking will now correctly count all instances of the same type of
cargo for different missions. Previously it only counted the cargo for
the last mission requiring that cargo type, as found in Cargo.json.</p>
</li>
<li>
<p>The loaded contents of Cargo.json can now be found in <code>monitor.state['CargoJSON']</code>.
<code>monitor.state</code> is what is passed to plugins as <code>state</code> in the
<code>journal_entry()</code> call.</p>
</li>
<li>
<p>Our logging code should now cope with logging from a property.</p>
</li>
<li>
<p>Logging from any name-mangled method should now work properly.</p>
</li>
<li>
<p>Miscellaneous updates to PLUGINS.md - mostly to clarify some things.</p>
</li>
</ul>
]]>
</description>
<enclosure
url="https://github.com/EDCD/EDMarketConnector/releases/download/Release/4.2.3/EDMarketConnector_win_4.2.3.msi"
sparkle:os="windows"
sparkle:installerArguments="/passive LAUNCH=yes"
sparkle:version="4.2.3"
length="11382784"
type="application/octet-stream"
/>
</item>
</channel>
</rss>
+38 -15
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,18 +56,21 @@ 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',
'atdumbfiremissile': 'AX Missile Rack',
'atmulticannon': 'AX Multi-Cannon',
('atmulticannon', 'v2'): 'Enhanced AX Multi-Cannon',
('atdumbfiremissile', 'v2'): 'Enhanced AX Missile Rack',
'basicmissilerack': 'Seeker Missile Rack',
'beamlaser': 'Beam Laser',
('beamlaser', 'heat'): 'Retributor Beam Laser',
@@ -88,6 +90,8 @@ outfitting_weapon_map = {
'mining_abrblstr': 'Abrasion Blaster',
'mining_seismchrgwarhd': 'Seismic Charge Launcher',
'mining_subsurfdispmisle': 'Sub-Surface Displacement Missile',
'human_extraction': 'Sub-Surface Extraction Missile',
'atventdisruptorpylon': 'Guardian Nanite Torpedo Pylon',
'mininglaser': 'Mining Laser',
('mininglaser', 'advanced'): 'Mining Lance Beam Laser',
'multicannon': 'Multi-Cannon',
@@ -209,7 +213,9 @@ outfitting_weaponrating_map = {
'hpt_mininglaser_turret_small': 'D',
'hpt_mininglaser_turret_medium': 'D',
'hpt_multicannon_fixed_small': 'F',
'hpt_multicannon_fixed_small_advanced': 'F',
'hpt_multicannon_fixed_medium': 'E',
'hpt_multicannon_fixed_medium_advanced': 'E',
'hpt_multicannon_fixed_large': 'C',
'hpt_multicannon_fixed_huge': 'A',
'hpt_multicannon_gimbal_small': 'G',
@@ -266,6 +272,11 @@ outfitting_weaponrating_map = {
'hpt_slugshot_turret_medium': 'D',
'hpt_slugshot_turret_large': 'C',
'hpt_xenoscannermk2_basic_tiny': '?',
'hpt_atmulticannon_gimbal_large': 'C',
'hpt_atmulticannon_gimbal_medium': 'E',
'hpt_human_extraction_fixed_medium': 'B',
'hpt_atventdisruptorpylon_fixed_medium': 'I',
'hpt_atventdisruptorpylon_fixed_large': 'I',
}
# Old standard weapon variants
@@ -278,13 +289,14 @@ outfitting_weaponoldvariant_map = {
}
outfitting_countermeasure_map = {
'antiunknownshutdown': ('Shutdown Field Neutraliser', 'F'),
'chafflauncher': ('Chaff Launcher', 'I'),
'electroniccountermeasure': ('Electronic Countermeasure', 'F'),
'heatsinklauncher': ('Heat Sink Launcher', 'I'),
'plasmapointdefence': ('Point Defence', 'I'),
'xenoscanner': ('Xeno Scanner', 'E'),
'xenoscannermk2': ('Unknown Xeno Scanner Mk II', '?'),
'antiunknownshutdown': ('Shutdown Field Neutraliser', 'F'),
('antiunknownshutdown', 'v2'): ('Thargoid Pulse Neutraliser', 'E'),
'chafflauncher': ('Chaff Launcher', 'I'),
'electroniccountermeasure': ('Electronic Countermeasure', 'F'),
'heatsinklauncher': ('Heat Sink Launcher', 'I'),
'plasmapointdefence': ('Point Defence', 'I'),
'xenoscanner': ('Xeno Scanner', 'E'),
'xenoscannermk2': ('Unknown Xeno Scanner Mk II', '?'),
}
outfitting_utility_map = {
@@ -347,6 +359,7 @@ outfitting_standard_map = {
'guardianpowerdistributor': 'Guardian Hybrid Power Distributor',
'guardianpowerplant': 'Guardian Hybrid Power Plant',
'hyperdrive': 'Frame Shift Drive',
('hyperdrive', 'overcharge'): 'Frame Shift Drive (SCO)',
'lifesupport': 'Life Support',
# 'planetapproachsuite': handled separately
'powerdistributor': 'Power Distributor',
@@ -376,6 +389,11 @@ outfitting_internal_map = {
'refinery': 'Refinery',
'recon': 'Recon Limpet Controller',
'repair': 'Repair Limpet Controller',
'rescue': 'Rescue Limpet Controller',
'mining': 'Mining Multi Limpet Controller',
'xeno': 'Xeno Multi Limpet Controller',
'operations': 'Operations Multi Limpet Controller',
'universal': 'Universal Multi Limpet Controller',
'repairer': 'Auto Field-Maintenance Unit',
'resourcesiphon': 'Hatch Breaker Limpet Controller',
'shieldcellbank': 'Shield Cell Bank',
@@ -383,6 +401,7 @@ outfitting_internal_map = {
('shieldgenerator', 'fast'): 'Bi-Weave Shield Generator',
('shieldgenerator', 'strong'): 'Prismatic Shield Generator',
'unkvesselresearch': 'Research Limpet Controller',
'expmodulestabiliser': 'Experimental Weapon Stabiliser',
}
# Dashboard Flags constants
@@ -460,6 +479,7 @@ ship_name_map = {
'belugaliner': 'Beluga Liner',
'cobramkiii': 'Cobra MkIII',
'cobramkiv': 'Cobra MkIV',
'cobramkv': 'Cobra MkV',
'clipper': 'Panther Clipper',
'cutter': 'Imperial Cutter',
'diamondback': 'Diamondback Scout',
@@ -482,13 +502,16 @@ ship_name_map = {
'krait_mkii': 'Krait MkII',
'krait_light': 'Krait Phantom',
'mamba': 'Mamba',
'mandalay': 'Mandalay',
'orca': 'Orca',
'python': 'Python',
'python_nx': 'Python Mk II',
'scout': 'Taipan Fighter',
'sidewinder': 'Sidewinder',
'testbuggy': 'Scarab',
'type6': 'Type-6 Transporter',
'type7': 'Type-7 Transporter',
'type8': 'Type-8 Transporter',
'type9': 'Type-9 Heavy',
'type9_military': 'Type-10 Defender',
'typex': 'Alliance Chieftain',
+14 -10
View File
@@ -24,7 +24,7 @@ __Module = dict[str, Union[str, list[str]]] # Have to keep old-style here for c
ship_map = ship_name_map.copy()
# Ship masses
ships_file = config.respath_path / "resources" / "ships.json"
ships_file = config.respath_path / "ships.json"
with open(ships_file, encoding="utf-8") as ships_file_handle:
ships = json.load(ships_file_handle)
@@ -106,7 +106,7 @@ def export(data, filename=None) -> None: # noqa: C901, CCR001
else:
name = module['name'] # type: ignore
if name == 'Frame Shift Drive':
if name == 'Frame Shift Drive' or name == 'Frame Shift Drive (SCO)':
fsd = module # save for range calculation
if mods.get('OutfittingFieldType_FSDOptimalMass'):
@@ -167,15 +167,19 @@ def export(data, filename=None) -> None: # noqa: C901, CCR001
try:
mass += ships[ship_name_map[data['ship']['name'].lower()]]['hullMass']
string += f'Mass : {mass:.2f} T empty\n {mass + fuel + cargo:.2f} T full\n'
maxfuel = fsd.get('maxfuel', 0) # type: ignore
fuelmul = fsd.get('fuelmul', 0) # type: ignore
multiplier = pow(min(fuel, fsd['maxfuel']) / fsd['fuelmul'], 1.0 # type: ignore
/ fsd['fuelpower']) * fsd['optmass'] # type: ignore
range_unladen = multiplier / (mass + fuel) + jumpboost
range_laden = multiplier / (mass + fuel + cargo) + jumpboost
# As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with
# a locale that uses comma for decimal separator.
string += f'Range : {range_unladen:.2f} LY unladen\n {range_laden:.2f} LY laden\n'
try:
multiplier = pow(min(fuel, maxfuel) / fuelmul, 1.0 / fsd['fuelpower']) * fsd['optmass'] # type: ignore
range_unladen = multiplier / (mass + fuel) + jumpboost
range_laden = multiplier / (mass + fuel + cargo) + jumpboost
# As of 2021-04-07 edsy.org says text import not yet implemented, so ignore the possible issue with
# a locale that uses comma for decimal separator.
except ZeroDivisionError:
range_unladen = range_laden = 0.0
string += (f'Range : {range_unladen:.2f} LY unladen\n'
f' {range_laden:.2f} LY laden\n')
except Exception:
if __debug__:
-4
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()
-276
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()
+62 -86
View File
@@ -8,7 +8,11 @@ import sys
import threading
import tkinter as tk
import winsound
from ctypes.wintypes import DWORD, HWND, LONG, LPWSTR, MSG, ULONG, WORD
from ctypes.wintypes import DWORD, LONG, MSG, ULONG, WORD, HWND, BOOL, UINT
import pywintypes
import win32api
import win32gui
import win32con
from config import config
from EDMCLogging import get_main_logger
from hotkey import AbstractHotkeyMgr
@@ -17,53 +21,22 @@ assert sys.platform == 'win32'
logger = get_main_logger()
RegisterHotKey = ctypes.windll.user32.RegisterHotKey
UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey
MOD_ALT = 0x0001
MOD_CONTROL = 0x0002
MOD_SHIFT = 0x0004
MOD_WIN = 0x0008
UnregisterHotKey = ctypes.windll.user32.UnregisterHotKey # TODO: Coming Soon
UnregisterHotKey.argtypes = [HWND, ctypes.c_int]
UnregisterHotKey.restype = BOOL
MOD_NOREPEAT = 0x4000
GetMessage = ctypes.windll.user32.GetMessageW
TranslateMessage = ctypes.windll.user32.TranslateMessage
DispatchMessage = ctypes.windll.user32.DispatchMessageW
PostThreadMessage = ctypes.windll.user32.PostThreadMessageW
WM_QUIT = 0x0012
WM_HOTKEY = 0x0312
WM_APP = 0x8000
WM_SND_GOOD = WM_APP + 1
WM_SND_BAD = WM_APP + 2
GetKeyState = ctypes.windll.user32.GetKeyState
MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
VK_BACK = 0x08
VK_CLEAR = 0x0c
VK_RETURN = 0x0d
VK_SHIFT = 0x10
VK_CONTROL = 0x11
VK_MENU = 0x12
VK_CAPITAL = 0x14
VK_MODECHANGE = 0x1f
VK_ESCAPE = 0x1b
VK_SPACE = 0x20
VK_DELETE = 0x2e
VK_LWIN = 0x5b
VK_RWIN = 0x5c
VK_NUMPAD0 = 0x60
VK_DIVIDE = 0x6f
VK_F1 = 0x70
VK_F24 = 0x87
WM_SND_GOOD = win32con.WM_APP + 1
WM_SND_BAD = win32con.WM_APP + 2
VK_OEM_MINUS = 0xbd
VK_NUMLOCK = 0x90
VK_SCROLL = 0x91
VK_PROCESSKEY = 0xe5
VK_OEM_CLEAR = 0xfe
GetForegroundWindow = ctypes.windll.user32.GetForegroundWindow
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
# VirtualKey mapping values
# <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeyexa>
MAPVK_VK_TO_VSC = 0
MAPVK_VSC_TO_VK = 1
MAPVK_VK_TO_CHAR = 2
MAPVK_VSC_TO_VK_EX = 3
MAPVK_VK_TO_VSC_EX = 4
def window_title(h) -> str:
@@ -74,11 +47,7 @@ def window_title(h) -> str:
:return: Window title.
"""
if h:
title_length = GetWindowTextLength(h) + 1
buf = ctypes.create_unicode_buffer(title_length)
if GetWindowText(h, buf, title_length):
return buf.value
return win32gui.GetWindowText(h)
return ''
@@ -138,6 +107,7 @@ class INPUT(ctypes.Structure):
SendInput = ctypes.windll.user32.SendInput
SendInput.argtypes = [ctypes.c_uint, ctypes.POINTER(INPUT), ctypes.c_int]
SendInput.restype = UINT
INPUT_MOUSE = 0
INPUT_KEYBOARD = 1
@@ -197,7 +167,7 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
logger.debug('Thread is/was running')
self.thread = None # type: ignore
logger.debug('Telling thread WM_QUIT')
PostThreadMessage(thread.ident, WM_QUIT, 0, 0)
win32gui.PostThreadMessage(thread.ident, win32con.WM_QUIT, 0, 0)
logger.debug('Joining thread')
thread.join() # Wait for it to unregister hotkey and quit
@@ -210,8 +180,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
"""Handle hotkeys."""
logger.debug('Begin...')
# Hotkey must be registered by the thread that handles it
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode):
logger.debug("We're not the right thread?")
try:
win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
except pywintypes.error:
logger.exception("We're not the right thread?")
self.thread = None # type: ignore
return
@@ -219,14 +191,14 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
msg = MSG()
logger.debug('Entering GetMessage() loop...')
while GetMessage(ctypes.byref(msg), None, 0, 0) != 0:
while win32gui.GetMessage(ctypes.byref(msg), None, 0, 0) != 0:
logger.debug('Got message')
if msg.message == WM_HOTKEY:
if msg.message == win32con.WM_HOTKEY:
logger.debug('WM_HOTKEY')
if (
config.get_int('hotkey_always')
or window_title(GetForegroundWindow()).startswith('Elite - Dangerous')
config.get_int('hotkey_always')
or window_title(win32gui.GetForegroundWindow()).startswith('Elite - Dangerous')
):
if not config.shutting_down:
logger.debug('Sending event <<Invoke>>')
@@ -236,8 +208,10 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
logger.debug('Passing key on')
UnregisterHotKey(None, 1)
SendInput(1, fake, ctypes.sizeof(INPUT))
if not RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode):
logger.debug("We aren't registered for this ?")
try:
win32gui.RegisterHotKey(None, 1, modifiers | MOD_NOREPEAT, keycode)
except pywintypes.error:
logger.exception("We aren't registered for this ?")
break
elif msg.message == WM_SND_GOOD:
@@ -250,8 +224,8 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
else:
logger.debug('Something else')
TranslateMessage(ctypes.byref(msg))
DispatchMessage(ctypes.byref(msg))
win32gui.TranslateMessage(ctypes.byref(msg))
win32gui.DispatchMessage(ctypes.byref(msg))
logger.debug('Exited GetMessage() loop.')
UnregisterHotKey(None, 1)
@@ -277,40 +251,42 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
:param event: tk event ?
:return: False to retain previous, None to not use, else (keycode, modifiers)
"""
modifiers = ((GetKeyState(VK_MENU) & 0x8000) and MOD_ALT) \
| ((GetKeyState(VK_CONTROL) & 0x8000) and MOD_CONTROL) \
| ((GetKeyState(VK_SHIFT) & 0x8000) and MOD_SHIFT) \
| ((GetKeyState(VK_LWIN) & 0x8000) and MOD_WIN) \
| ((GetKeyState(VK_RWIN) & 0x8000) and MOD_WIN)
modifiers = ((win32api.GetKeyState(win32con.VK_MENU) & 0x8000) and win32con.MOD_ALT) \
| ((win32api.GetKeyState(win32con.VK_CONTROL) & 0x8000) and win32con.MOD_CONTROL) \
| ((win32api.GetKeyState(win32con.VK_SHIFT) & 0x8000) and win32con.MOD_SHIFT) \
| ((win32api.GetKeyState(win32con.VK_LWIN) & 0x8000) and win32con.MOD_WIN) \
| ((win32api.GetKeyState(win32con.VK_RWIN) & 0x8000) and win32con.MOD_WIN)
keycode = event.keycode
if keycode in [VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN]:
if keycode in (win32con.VK_SHIFT, win32con.VK_CONTROL, win32con.VK_MENU, win32con.VK_LWIN, win32con.VK_RWIN):
return 0, modifiers
if not modifiers:
if keycode == VK_ESCAPE: # Esc = retain previous
if keycode == win32con.VK_ESCAPE: # Esc = retain previous
return False
if keycode in [VK_BACK, VK_DELETE, VK_CLEAR, VK_OEM_CLEAR]: # BkSp, Del, Clear = clear hotkey
if keycode in (win32con.VK_BACK, win32con.VK_DELETE,
win32con.VK_CLEAR, win32con.VK_OEM_CLEAR): # BkSp, Del, Clear = clear hotkey
return None
if (
keycode in [VK_RETURN, VK_SPACE, VK_OEM_MINUS] or ord('A') <= keycode <= ord('Z')
keycode in (win32con.VK_RETURN, win32con.VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z')
): # don't allow keys needed for typing in System Map
winsound.MessageBeep()
return None
if (keycode in [VK_NUMLOCK, VK_SCROLL, VK_PROCESSKEY]
or VK_CAPITAL <= keycode <= VK_MODECHANGE): # ignore unmodified mode switch keys
if (keycode in (win32con.VK_NUMLOCK, win32con.VK_SCROLL, win32con.VK_PROCESSKEY)
or win32con.VK_CAPITAL <= keycode <= win32con.VK_MODECHANGE): # ignore unmodified mode switch keys
return 0, modifiers
# See if the keycode is usable and available
if RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode):
try:
win32gui.RegisterHotKey(None, 2, modifiers | MOD_NOREPEAT, keycode)
UnregisterHotKey(None, 2)
return keycode, modifiers
winsound.MessageBeep()
return None
except pywintypes.error:
winsound.MessageBeep()
return None
def display(self, keycode, modifiers) -> str:
"""
@@ -321,32 +297,32 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
:return: string form
"""
text = ''
if modifiers & MOD_WIN:
if modifiers & win32con.MOD_WIN:
text += '❖+'
if modifiers & MOD_CONTROL:
if modifiers & win32con.MOD_CONTROL:
text += 'Ctrl+'
if modifiers & MOD_ALT:
if modifiers & win32con.MOD_ALT:
text += 'Alt+'
if modifiers & MOD_SHIFT:
if modifiers & win32con.MOD_SHIFT:
text += '⇧+'
if VK_NUMPAD0 <= keycode <= VK_DIVIDE:
if win32con.VK_NUMPAD0 <= keycode <= win32con.VK_DIVIDE:
text += '№'
if not keycode:
pass
elif VK_F1 <= keycode <= VK_F24:
text += f'F{keycode + 1 - VK_F1}'
elif win32con.VK_F1 <= keycode <= win32con.VK_F24:
text += f'F{keycode + 1 - win32con.VK_F1}'
elif keycode in WindowsHotkeyMgr.DISPLAY: # specials
text += WindowsHotkeyMgr.DISPLAY[keycode]
else:
c = MapVirtualKey(keycode, 2) # printable ?
c = win32api.MapVirtualKey(keycode, MAPVK_VK_TO_CHAR)
if not c: # oops not printable
text += '⁈'
@@ -361,9 +337,9 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
def play_good(self) -> None:
"""Play the 'good' sound."""
if self.thread:
PostThreadMessage(self.thread.ident, WM_SND_GOOD, 0, 0)
win32gui.PostThreadMessage(self.thread.ident, WM_SND_GOOD, 0, 0)
def play_bad(self) -> None:
"""Play the 'bad' sound."""
if self.thread:
PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
win32gui.PostThreadMessage(self.thread.ident, WM_SND_BAD, 0, 0)
BIN
View File
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

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
View File
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: 53 KiB

+6 -6
View File
@@ -5,23 +5,23 @@ Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
import os
import subprocess
from pathlib import Path
from build import build
def run_inno_setup_installer(iss_path: str) -> None:
def run_inno_setup_installer(iss_path: Path) -> None:
"""Run the Inno installer, building the installation exe."""
# Get the path to the Inno Setup compiler (iscc.exe) (Currently set to default path)
inno_setup_compiler_path: str = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe"
inno_setup_compiler_path = Path("C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe")
# Check if the Inno Setup compiler executable exists
if not os.path.isfile(inno_setup_compiler_path):
if not inno_setup_compiler_path.exists():
print(f"Error: Inno Setup compiler not found at '{inno_setup_compiler_path}'.")
return
# Check if the provided .iss file exists
if not os.path.isfile(iss_file_path):
if not iss_file_path.exists():
print(f"Error: The provided .iss file '{iss_path}' not found.")
return
@@ -40,6 +40,6 @@ def run_inno_setup_installer(iss_path: str) -> None:
if __name__ == "__main__":
build()
# Add the ISS Template File
iss_file_path: str = "./EDMC_Installer_Config.iss"
iss_file_path = Path("./EDMC_Installer_Config.iss")
# Build the ISS file
run_inno_setup_installer(iss_file_path)
+8 -15
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)
+2 -2
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
+94 -86
View File
@@ -16,17 +16,13 @@ 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
from typing import TYPE_CHECKING, Iterable, TextIO, cast
import pathlib
from config import config
from EDMCLogging import get_main_logger
if TYPE_CHECKING:
def _(x: str) -> str: ...
# 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, '')
@@ -38,14 +34,9 @@ logger = get_main_logger()
# Language name
LANGUAGE_ID = '!Language'
LOCALISATION_DIR = 'L10n'
LOCALISATION_DIR: pathlib.Path = pathlib.Path('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:
@@ -58,7 +49,6 @@ elif sys.platform == 'win32':
GetUserPreferredUILanguages.argtypes = [
DWORD, ctypes.POINTER(ctypes.c_ulong), LPCVOID, ctypes.POINTER(ctypes.c_ulong)
]
GetUserPreferredUILanguages.restype = BOOL
LOCALE_NAME_USER_DEFAULT = None
@@ -67,7 +57,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 +84,7 @@ class _Translations:
Use when translation is not desired or not available
"""
self.translations = {None: {}}
# DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later.
builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n')
def install(self, lang: str | None = None) -> None: # noqa: CCR001
@@ -94,7 +94,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():
@@ -117,10 +117,10 @@ class _Translations:
self.translations = {None: self.contents(cast(str, lang))}
for plugin in listdir(config.plugin_dir_path):
plugin_path = join(config.plugin_dir_path, plugin, LOCALISATION_DIR)
if isdir(plugin_path):
plugin_path = config.plugin_dir_path / plugin / LOCALISATION_DIR
if pathlib.Path.is_dir(plugin_path):
try:
self.translations[plugin] = self.contents(cast(str, lang), str(plugin_path))
self.translations[plugin] = self.contents(cast(str, lang), plugin_path)
except UnicodeDecodeError as e:
logger.warning(f'Malformed file {lang}.strings in plugin {plugin}: {e}')
@@ -128,9 +128,10 @@ class _Translations:
except Exception:
logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}')
# DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later.
builtins.__dict__['_'] = self.translate
def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
def contents(self, lang: str, plugin_path: pathlib.Path | None = None) -> dict[str, str]:
"""Load all the translations from a translation file."""
assert lang in self.available()
translations = {}
@@ -141,12 +142,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 +156,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: pathlib.Path | 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 = 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,45 +204,36 @@ 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
return names
def respath(self) -> str:
def respath(self) -> pathlib.Path:
"""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))
return pathlib.Path(sys.executable).parent.joinpath(LOCALISATION_DIR).resolve()
if __file__:
return abspath(join(dirname(__file__), LOCALISATION_DIR))
return pathlib.Path(__file__).parent.joinpath(LOCALISATION_DIR).resolve()
return abspath(LOCALISATION_DIR)
return LOCALISATION_DIR.resolve()
def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None:
def file(self, lang: str, plugin_path: pathlib.Path | None = None) -> TextIO | None:
"""
Open the given lang file for reading.
@@ -226,8 +242,8 @@ class _Translations:
:return: the opened file (Note: This should be closed when done)
"""
if plugin_path:
file_path = join(plugin_path, f'{lang}.strings')
if not exists(file_path):
file_path = plugin_path / f"{lang}.strings"
if not file_path.exists():
return None
try:
@@ -235,36 +251,26 @@ 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')
res_path = self.respath() / f'{lang}.strings'
return open(res_path, encoding='utf-8')
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)
# DEPRECATED: Migrate to _Locale.string_from_number. Will remove in 6.0 or later.
def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802
warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
warnings.warn('use _Locale.string_from_number instead.', DeprecationWarning, stacklevel=2)
return self.string_from_number(number, decimals) # type: ignore
# DEPRECATED: Migrate to _Locale.number_from_string. Will remove in 6.0 or later.
def numberFromString(self, string: str) -> int | float | None: # noqa: N802
warnings.warn(DeprecationWarning('use _Locale.number_from_string instead.'))
warnings.warn('use _Locale.number_from_string instead.', DeprecationWarning, stacklevel=2)
return self.number_from_string(string)
# DEPRECATED: Migrate to _Locale.preferred_languages. Will remove in 6.0 or later.
def preferredLanguages(self) -> Iterable[str]: # noqa: N802
warnings.warn(DeprecationWarning('use _Locale.preferred_languages instead.'))
warnings.warn('use _Locale.preferred_languages instead.', DeprecationWarning, stacklevel=2)
return self.preferred_languages()
def string_from_number(self, number: float | int, decimals: int = 5) -> str:
@@ -280,14 +286,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 +298,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 +328,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,17 +358,32 @@ class _Locale:
# singletons
Locale = _Locale()
Translations = _Translations()
translations = Translations()
# DEPRECATED: Migrate to `translations`. Will be removed in 6.0 or later.
# 'Translations' singleton is deprecated.
# Begin Deprecation Zone
class _Translations(Translations):
def __init__(self):
warnings.warn('Translations and _Translations() are deprecated. '
'Please use translations and Translations() instead.', DeprecationWarning, stacklevel=2)
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__":
regexp = re.compile(r'''_\([ur]?(['"])(((?<!\\)\\\1|.)+?)\1\)[^#]*(#.+)?''') # match a single line python literal
seen: dict[str, str] = {}
plugin_dir = pathlib.Path('plugins')
for f in (
sorted(x for x in listdir('.') if x.endswith('.py')) +
sorted(join('plugins', x) for x in (listdir('plugins') if isdir('plugins') else []) if x.endswith('.py'))
sorted(plugin_dir.glob('*.py')) if plugin_dir.is_dir() else []
):
with open(f, encoding='utf-8') as h:
lineno = 0
@@ -384,11 +392,11 @@ if __name__ == "__main__":
match = regexp.search(line)
if match and not seen.get(match.group(2)): # only record first commented instance of a string
seen[match.group(2)] = (
(match.group(4) and (match.group(4)[1:].strip()) + '. ' or '') + f'[{basename(f)}]'
(match.group(4) and (match.group(4)[1:].strip()) + '. ' or '') + f'[{pathlib.Path(f).name}]'
)
if seen:
target_path = join(LOCALISATION_DIR, 'en.template.new')
makedirs(dirname(target_path), exist_ok=True)
target_path = LOCALISATION_DIR / 'en.template.new'
target_path.parent.mkdir(parents=True, exist_ok=True)
with open(target_path, 'w', encoding='utf-8') as target_file:
target_file.write(f'/* Language name */\n"{LANGUAGE_ID}" = "English";\n\n')
for thing in sorted(seen, key=str.lower):
+4 -4
View File
@@ -11,7 +11,7 @@ import json
import re
import time
from os import listdir
from os.path import join
from pathlib import Path
import companion
import util_ships
from config import config
@@ -45,7 +45,7 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N
regexp = re.compile(re.escape(ship) + r'\.\d\d\d\d-\d\d-\d\dT\d\d\.\d\d\.\d\d\.txt')
oldfiles = sorted([x for x in listdir(config.get_str('outdir')) if regexp.match(x)])
if oldfiles:
with open(join(config.get_str('outdir'), oldfiles[-1]), 'rU') as h:
with open(Path(config.get_str('outdir')) / Path(oldfiles[-1]), 'rU') as h:
if h.read() == string:
return # same as last time - don't write
@@ -53,9 +53,9 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N
# Write
output_directory = config.get_str('outdir')
output_directory = Path(config.get_str('outdir'))
ship_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(query_time))
file_path = join(output_directory, f"{ship}.{ship_time}.txt")
file_path = output_directory / f"{ship}.{ship_time}.txt"
with open(file_path, 'wt') as h:
h.write(string)
+361
View File
@@ -326,30 +326,69 @@
"hpt_antiunknownshutdown_tiny": {
"mass": 1.3
},
"hpt_antiunknownshutdown_tiny_v2": {
"mass": 3
},
"hpt_atdumbfiremissile_fixed_large": {
"mass": 8
},
"hpt_atdumbfiremissile_fixed_large_v2": {
"mass": 8
},
"hpt_atdumbfiremissile_fixed_medium": {
"mass": 4
},
"hpt_atdumbfiremissile_fixed_medium_v2": {
"mass": 4
},
"hpt_atdumbfiremissile_turret_large": {
"mass": 8
},
"hpt_atdumbfiremissile_turret_large_v2": {
"mass": 8
},
"hpt_atdumbfiremissile_turret_medium": {
"mass": 4
},
"hpt_atdumbfiremissile_turret_medium_v2": {
"mass": 4
},
"hpt_atmulticannon_fixed_large": {
"mass": 8
},
"hpt_atmulticannon_fixed_large_v2": {
"mass": 8
},
"hpt_atmulticannon_fixed_medium": {
"mass": 4
},
"hpt_atmulticannon_fixed_medium_v2": {
"mass": 4
},
"hpt_atmulticannon_gimbal_large": {
"mass": 8
},
"hpt_atmulticannon_gimbal_medium": {
"mass": 4
},
"hpt_atmulticannon_turret_large": {
"mass": 8
},
"hpt_atmulticannon_turret_large_v2": {
"mass": 8
},
"hpt_atmulticannon_turret_medium": {
"mass": 4
},
"hpt_atmulticannon_turret_medium_v2": {
"mass": 4
},
"hpt_atventdisruptorpylon_fixed_large": {
"mass": 5
},
"hpt_atventdisruptorpylon_fixed_medium": {
"mass": 3
},
"hpt_basicmissilerack_fixed_large": {
"mass": 8
},
@@ -416,6 +455,9 @@
"hpt_cannon_gimbal_medium": {
"mass": 4
},
"hpt_cannon_gimbal_medium_burst": {
"mass": 4
},
"hpt_cannon_gimbal_small": {
"mass": 2
},
@@ -446,6 +488,9 @@
"hpt_causticmissile_fixed_medium": {
"mass": 4
},
"hpt_causticsinklauncher_turret_tiny": {
"mass": 1.7
},
"hpt_chafflauncher_tiny": {
"mass": 1.3
},
@@ -608,6 +653,12 @@
"hpt_mininglaser_turret_small": {
"mass": 2
},
"hpt_missing_hardpoint": {
"mass": 0
},
"hpt_missing_utility": {
"mass": 0
},
"hpt_mrascanner_size0_class1": {
"mass": 1.3
},
@@ -833,6 +884,9 @@
"hpt_slugshot_turret_small": {
"mass": 2
},
"hpt_xenoscanner_advanced_tiny": {
"mass": 3
},
"hpt_xenoscanner_basic_tiny": {
"mass": 1.3
},
@@ -905,6 +959,12 @@
"int_corrosionproofcargorack_size4_class1": {
"mass": 0
},
"int_corrosionproofcargorack_size5_class1": {
"mass": 0
},
"int_corrosionproofcargorack_size6_class1": {
"mass": 0
},
"int_detailedsurfacescanner_tiny": {
"mass": 0
},
@@ -1352,6 +1412,12 @@
"int_engine_size8_class5": {
"mass": 160
},
"int_expmodulestabiliser_size3_class3": {
"mass": 8
},
"int_expmodulestabiliser_size5_class3": {
"mass": 20
},
"int_fighterbay_size5_class1": {
"mass": 20
},
@@ -1750,6 +1816,216 @@
"int_hullreinforcement_size5_class2": {
"mass": 16
},
"int_hyperdrive_overcharge_size2_class1": {
"mass": 2.5,
"optmass": 60,
"maxfuel": 0.6,
"fuelmul": 0.008,
"fuelpower": 2
},
"int_hyperdrive_overcharge_size2_class2": {
"mass": 2.5,
"optmass": 90,
"maxfuel": 0.9,
"fuelmul": 0.012,
"fuelpower": 2
},
"int_hyperdrive_overcharge_size2_class3": {
"mass": 2.5,
"optmass": 90,
"maxfuel": 0.9,
"fuelmul": 0.012,
"fuelpower": 2
},
"int_hyperdrive_overcharge_size2_class4": {
"mass": 2.5,
"optmass": 90,
"maxfuel": 0.9,
"fuelmul": 0.012,
"fuelpower": 2
},
"int_hyperdrive_overcharge_size2_class5": {
"mass": 2.5,
"optmass": 100,
"maxfuel": 1,
"fuelmul": 0.013,
"fuelpower": 2
},
"int_hyperdrive_overcharge_size3_class1": {
"mass": 5,
"optmass": 100,
"maxfuel": 1.2,
"fuelmul": 0.008,
"fuelpower": 2.15
},
"int_hyperdrive_overcharge_size3_class2": {
"mass": 2,
"optmass": 150,
"maxfuel": 1.8,
"fuelmul": 0.012,
"fuelpower": 2.15
},
"int_hyperdrive_overcharge_size3_class3": {
"mass": 5,
"optmass": 150,
"maxfuel": 1.8,
"fuelmul": 0.012,
"fuelpower": 2.15
},
"int_hyperdrive_overcharge_size3_class4": {
"mass": 5,
"optmass": 150,
"maxfuel": 1.8,
"fuelmul": 0.012,
"fuelpower": 2.15
},
"int_hyperdrive_overcharge_size3_class5": {
"mass": 5,
"optmass": 167,
"maxfuel": 1.9,
"fuelmul": 0.013,
"fuelpower": 2.15
},
"int_hyperdrive_overcharge_size4_class1": {
"mass": 10,
"optmass": 350,
"maxfuel": 2,
"fuelmul": 0.008,
"fuelpower": 2.3
},
"int_hyperdrive_overcharge_size4_class2": {
"mass": 4,
"optmass": 525,
"maxfuel": 3,
"fuelmul": 0.012,
"fuelpower": 2.3
},
"int_hyperdrive_overcharge_size4_class3": {
"mass": 10,
"optmass": 525,
"maxfuel": 3,
"fuelmul": 0.012,
"fuelpower": 2.3
},
"int_hyperdrive_overcharge_size4_class4": {
"mass": 10,
"optmass": 525,
"maxfuel": 3,
"fuelmul": 0.012,
"fuelpower": 2.3
},
"int_hyperdrive_overcharge_size4_class5": {
"mass": 10,
"optmass": 585,
"maxfuel": 3.2,
"fuelmul": 0.013,
"fuelpower": 2.3
},
"int_hyperdrive_overcharge_size5_class1": {
"mass": 20,
"optmass": 700,
"maxfuel": 3.3,
"fuelmul": 0.008,
"fuelpower": 2.45
},
"int_hyperdrive_overcharge_size5_class2": {
"mass": 8,
"optmass": 1050,
"maxfuel": 5,
"fuelmul": 0.012,
"fuelpower": 2.45
},
"int_hyperdrive_overcharge_size5_class3": {
"mass": 20,
"optmass": 1050,
"maxfuel": 5,
"fuelmul": 0.012,
"fuelpower": 2.45
},
"int_hyperdrive_overcharge_size5_class4": {
"mass": 20,
"optmass": 1050,
"maxfuel": 5,
"fuelmul": 0.012,
"fuelpower": 2.45
},
"int_hyperdrive_overcharge_size5_class5": {
"mass": 20,
"optmass": 1175,
"maxfuel": 5.2,
"fuelmul": 0.013,
"fuelpower": 2.45
},
"int_hyperdrive_overcharge_size6_class1": {
"mass": 40,
"optmass": 1200,
"maxfuel": 5.3,
"fuelmul": 0.008,
"fuelpower": 2.6
},
"int_hyperdrive_overcharge_size6_class2": {
"mass": 16,
"optmass": 1800,
"maxfuel": 8,
"fuelmul": 0.012,
"fuelpower": 2.6
},
"int_hyperdrive_overcharge_size6_class3": {
"mass": 40,
"optmass": 1800,
"maxfuel": 8,
"fuelmul": 0.012,
"fuelpower": 2.6
},
"int_hyperdrive_overcharge_size6_class4": {
"mass": 40,
"optmass": 1800,
"maxfuel": 8,
"fuelmul": 0.012,
"fuelpower": 2.6
},
"int_hyperdrive_overcharge_size6_class5": {
"mass": 40,
"optmass": 2000,
"maxfuel": 8.3,
"fuelmul": 0.013,
"fuelpower": 2.6
},
"int_hyperdrive_overcharge_size7_class1": {
"mass": 80,
"optmass": 1800,
"maxfuel": 8.5,
"fuelmul": 0.008,
"fuelpower": 2.75
},
"int_hyperdrive_overcharge_size7_class2": {
"mass": 32,
"optmass": 2700,
"maxfuel": 12.8,
"fuelmul": 0.012,
"fuelpower": 2.75
},
"int_hyperdrive_overcharge_size7_class3": {
"mass": 80,
"optmass": 2700,
"maxfuel": 12.8,
"fuelmul": 0.012,
"fuelpower": 2.75
},
"int_hyperdrive_overcharge_size7_class4": {
"mass": 80,
"optmass": 2700,
"maxfuel": 12.8,
"fuelmul": 0.012,
"fuelpower": 2.75
},
"int_hyperdrive_overcharge_size7_class5": {
"mass": 80,
"optmass": 3000,
"maxfuel": 13.1,
"fuelmul": 0.013,
"fuelpower": 2.75
},
"int_hyperdrive_size2_class1": {
"mass": 2.5,
"optmass": 48,
@@ -2145,6 +2421,34 @@
"int_metaalloyhullreinforcement_size5_class2": {
"mass": 16
},
"int_missing_engine": {
"mass": 0
},
"int_missing_fueltank": {
"mass": 0
},
"int_missing_hyperdrive": {
"mass": 0,
"optmass": 0,
"maxfuel": 0,
"fuelmul": 0,
"fuelpower": 0
},
"int_missing_lifesupport": {
"mass": 0
},
"int_missing_module": {
"mass": 0
},
"int_missing_powerdistributor": {
"mass": 0
},
"int_missing_powerplant": {
"mass": 0
},
"int_missing_sensors": {
"mass": 0
},
"int_modulereinforcement_size1_class1": {
"mass": 2
},
@@ -2250,6 +2554,9 @@
"int_planetapproachsuite": {
"mass": 0
},
"int_planetapproachsuite_advanced": {
"mass": 0
},
"int_powerdistributor_size1_class1": {
"mass": 1.3
},
@@ -2895,6 +3202,15 @@
"int_shieldcellbank_size8_class5": {
"mass": 160
},
"int_shieldgenerator_size1_class1": {
"mass": 1.3
},
"int_shieldgenerator_size1_class2": {
"mass": 0.5
},
"int_shieldgenerator_size1_class3": {
"mass": 1.3
},
"int_shieldgenerator_size1_class3_fast": {
"mass": 1.3
},
@@ -3108,6 +3424,21 @@
"mamba_armour_reactive": {
"mass": 38
},
"mandalay_armour_grade1": {
"mass": 0
},
"mandalay_armour_grade2": {
"mass": 32
},
"mandalay_armour_grade3": {
"mass": 63
},
"mandalay_armour_mirrored": {
"mass": 63
},
"mandalay_armour_reactive": {
"mass": 63
},
"orca_armour_grade1": {
"mass": 0
},
@@ -3138,6 +3469,21 @@
"python_armour_reactive": {
"mass": 53
},
"python_nx_armour_grade1": {
"mass": 0
},
"python_nx_armour_grade2": {
"mass": 26
},
"python_nx_armour_grade3": {
"mass": 53
},
"python_nx_armour_mirrored": {
"mass": 53
},
"python_nx_armour_reactive": {
"mass": 53
},
"sidewinder_armour_grade1": {
"mass": 0
},
@@ -3183,6 +3529,21 @@
"type7_armour_reactive": {
"mass": 63
},
"type8_armour_grade1": {
"mass": 0
},
"type8_armour_grade2": {
"mass": 32
},
"type8_armour_grade3": {
"mass": 63
},
"type8_armour_mirrored": {
"mass": 63
},
"type8_armour_reactive": {
"mass": 63
},
"type9_armour_grade1": {
"mass": 0
},
BIN
View File
Binary file not shown.
+98 -79
View File
@@ -14,16 +14,18 @@ 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
from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping
import psutil
import semantic_version
import util_ships
from config import config
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised
from config import config, appname, appversion
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised, ship_name_map
from EDMCLogging import get_main_logger
from edshipyard import ships
if TYPE_CHECKING:
import tkinter
@@ -34,44 +36,17 @@ 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
if sys.platform == 'win32':
from watchdog.events import FileSystemEventHandler, FileSystemEvent
from watchdog.observers import Observer
from watchdog.observers.api import BaseObserver
F_GLOBAL_NOCACHE = 55
elif sys.platform == 'win32':
import ctypes
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
from watchdog.events import FileCreatedEvent, FileSystemEventHandler
from watchdog.observers import Observer
from watchdog.observers.api import BaseObserver
EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
CloseHandle = ctypes.windll.kernel32.CloseHandle
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
else:
# Linux's inotify doesn't work over CIFS or NFS, so poll
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
@@ -81,7 +56,8 @@ class EDLogs(FileSystemEventHandler):
"""Monitoring of Journal files."""
# Magic with FileSystemEventHandler can confuse type checkers when they do not have access to every import
_POLL = 1 # Polling is cheap, so do it often
_POLL = 1 # Polling while running is cheap, so do it often
_INACTIVE_POLL = 10 # Polling while not running isn't as cheap, so do it less often
_RE_CANONICALISE = re.compile(r'\$(.+)_name;')
_RE_CATEGORY = re.compile(r'\$MICRORESOURCE_CATEGORY_(.+);')
_RE_LOGFILE = re.compile(r'^Journal(Alpha|Beta)?\.[0-9]{2,4}(-)?[0-9]{2}(-)?[0-9]{2}(T)?[0-9]{2}[0-9]{2}[0-9]{2}'
@@ -111,6 +87,7 @@ class EDLogs(FileSystemEventHandler):
self.catching_up = False
self.game_was_running = False # For generation of the "ShutDown" event
self.running_process = None
# Context for journal handling
self.version: str | None = None
@@ -120,6 +97,7 @@ class EDLogs(FileSystemEventHandler):
self.group: str | None = None
self.cmdr: str | None = None
self.started: int | None = None # Timestamp of the LoadGame event
self.slef: str | None = None
self._navroute_retries_remaining = 0
self._last_navroute_journal_timestamp: float | None = None
@@ -163,6 +141,10 @@ class EDLogs(FileSystemEventHandler):
'ShipType': None,
'HullValue': None,
'ModulesValue': None,
'UnladenMass': None,
'CargoCapacity': None,
'MaxJumpRange': None,
'FuelCapacity': None,
'Rebuy': None,
'Modules': None,
'CargoJSON': None, # The raw data from the last time cargo.json was read
@@ -197,6 +179,13 @@ class EDLogs(FileSystemEventHandler):
'StationName': None,
'NavRoute': None,
'Powerplay': {
'Power': None,
'Rank': None,
'Merits': None,
'Votes': None,
'TimePledged': None,
},
}
def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001
@@ -357,11 +346,11 @@ 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)):
if not event.is_directory and self._RE_LOGFILE.search(str(basename(event.src_path))):
self.logfile = event.src_path
self.logfile = event.src_path # type: ignore
def worker(self) -> None: # noqa: C901, CCR001
"""
@@ -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,12 +470,12 @@ 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)
if self.game_was_running:
sleep(self._POLL)
else:
sleep(self._INACTIVE_POLL)
# Check whether we're still supposed to be running
if threading.current_thread() != self.thread:
@@ -567,7 +554,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()
@@ -704,6 +691,11 @@ class EDLogs(FileSystemEventHandler):
self.state['ShipType'] = self.canonicalise(entry['Ship'])
self.state['HullValue'] = entry.get('HullValue') # not present on exiting Outfitting
self.state['ModulesValue'] = entry.get('ModulesValue') # not present on exiting Outfitting
self.state['UnladenMass'] = entry.get('UnladenMass')
self.state['CargoCapacity'] = entry.get('CargoCapacity')
self.state['MaxJumpRange'] = entry.get('MaxJumpRange')
self.state["FuelCapacity"] = {name: entry.get("FuelCapacity", {}).get(name) for name in
("Main", "Reserve")}
self.state['Rebuy'] = entry.get('Rebuy')
# Remove spurious differences between initial Loadout event and subsequent
self.state['Modules'] = {}
@@ -717,6 +709,34 @@ class EDLogs(FileSystemEventHandler):
module.pop('AmmoInHopper')
self.state['Modules'][module['Slot']] = module
# SLEF
initial_dict: dict[str, dict[str, Any]] = {
"header": {"appName": appname, "appVersion": str(appversion())}
}
data_dict = {}
for module in entry['Modules']:
if module.get('Slot') == 'FuelTank':
cap = module['Item'].split('size')
cap = cap[1].split('_')
cap = 2 ** int(cap[0])
ship = ship_name_map[entry["Ship"]]
fuel = {'Main': cap, 'Reserve': ships[ship]['reserveFuelCapacity']}
data_dict.update({"FuelCapacity": fuel})
data_dict.update({
'Ship': entry["Ship"],
'ShipName': entry['ShipName'],
'ShipIdent': entry['ShipIdent'],
'HullValue': entry.get('HullValue'), # type: ignore
'ModulesValue': entry.get('ModulesValue'), # type: ignore
'Rebuy': entry['Rebuy'],
'MaxJumpRange': entry['MaxJumpRange'],
'UnladenMass': entry['UnladenMass'],
'CargoCapacity': entry['CargoCapacity'],
'Modules': entry['Modules'],
})
initial_dict.update({'data': data_dict})
output = json.dumps(initial_dict, indent=4)
self.slef = str(f"[{output}]")
elif event_type == 'modulebuy':
self.state['Modules'][entry['Slot']] = {
@@ -1042,7 +1062,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 +1093,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 +1128,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
@@ -1826,6 +1846,13 @@ class EDLogs(FileSystemEventHandler):
# There should be a `Backpack` event as you 'come to' in the
# new location, so no need to zero out BackPack here.
elif event_type == 'powerplay':
self.state['Powerplay']['Power'] = entry.get('Power', '')
self.state['Powerplay']['Rank'] = entry.get('Rank', 0)
self.state['Powerplay']['Merits'] = entry.get('Merits', 0)
self.state['Powerplay']['Votes'] = entry.get('Votes', 0)
self.state['Powerplay']['TimePledged'] = entry.get('TimePledged', 0)
return entry
except Exception as ex:
@@ -2136,41 +2163,33 @@ class EDLogs(FileSystemEventHandler):
return entry
def game_running(self) -> bool: # noqa: CCR001
def game_running(self) -> bool:
"""
Determine if the game is currently running.
TODO: Implement on Linux
: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':
def WindowTitle(h): # noqa: N802
if h:
length = GetWindowTextLength(h) + 1
buf = ctypes.create_unicode_buffer(length)
if GetWindowText(h, buf, length):
return buf.value
return None
def callback(hWnd, lParam): # noqa: N803
name = WindowTitle(hWnd)
if name and name.startswith('Elite - Dangerous'):
handle = GetProcessHandleFromHwnd(hWnd)
if handle: # If GetProcessHandleFromHwnd succeeds then the app is already running as this user
CloseHandle(handle)
return False # stop enumeration
return True
return not EnumWindows(EnumWindowsProc(callback), 0)
return False
if self.running_process:
p = self.running_process
try:
with p.oneshot():
if p.status() not in [psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING]:
raise psutil.NoSuchProcess(p.pid)
except psutil.NoSuchProcess:
# Process likely expired
self.running_process = None
if not self.running_process:
try:
edmc_process = psutil.Process()
edmc_user = edmc_process.username()
for proc in psutil.process_iter(['name', 'username']):
if 'EliteDangerous' in proc.info['name'] and proc.info['username'] == edmc_user:
self.running_process = proc
return True
except psutil.NoSuchProcess:
pass
return False
return bool(self.running_process)
def ship(self, timestamped=True) -> MutableMapping[str, Any] | None:
"""
@@ -2189,7 +2208,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())
+114 -112
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,12 @@ from __future__ import annotations
import sys
import tkinter as tk
from tkinter import ttk
import warnings
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 +26,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 +54,130 @@ 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):
warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
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):
warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
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)
+24 -11
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
@@ -57,7 +55,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
"""
# Lazily populate
if not moduledata:
modules_path = config.respath_path / "resources" / "modules.json"
modules_path = config.respath_path / "modules.json"
moduledata.update(json.loads(modules_path.read_text()))
if not module.get('name'):
@@ -69,7 +67,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
# Armour - e.g. Federation_Dropship_Armour_Grade2
if name[-2] == 'armour':
# Armour is ship-specific, and ship names can have underscores
ship_name, armour_grade = module["name"].lower().rsplit("_", 2)[0:2]
ship_name, armour, armour_grade = module["name"].lower().rsplit("_", 2)[0:3]
if ship_name not in ship_map:
raise AssertionError(f"Unknown ship: {ship_name}")
new['category'] = 'standard'
@@ -79,7 +77,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
new['rating'] = 'I'
# Skip uninteresting stuff - some no longer present in ED 3.1 cAPI data
elif (name[0] in [
elif (name[0] in (
'bobble',
'decal',
'nameplate',
@@ -87,7 +85,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
'enginecustomisation',
'voicepack',
'weaponcustomisation'
]
)
or name[1].startswith('shipkit')):
return None
@@ -100,6 +98,12 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
elif not entitled and name[1] == 'planetapproachsuite':
return None
# V2 Shutdown Field Neutralizer - Hpt_AntiUnknownShutdown_Tiny_V2
elif name[0] == 'hpt' and name[1] in countermeasure_map and len(name) == 4 and name[3] == 'v2':
new['category'] = 'utility'
new['name'], new['rating'] = countermeasure_map[name[1]]
new['class'] = weaponclass_map[name[-2]]
# Countermeasures - e.g. Hpt_PlasmaPointDefence_Turret_Tiny
elif name[0] == 'hpt' and name[1] in countermeasure_map:
new['category'] = 'utility'
@@ -181,12 +185,18 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
if name[1] == 'dronecontrol': # e.g. Int_DroneControl_Collection_Size1_Class1
name.pop(0)
elif name[1] == 'multidronecontrol': # e.g. Int_MultiDroneControl_Rescue_Size3_Class3
name.pop(0)
elif name[-1] == 'free': # Starter Sidewinder or Freagle modules - just treat them like vanilla modules
name.pop()
if name[1] in standard_map: # e.g. Int_Engine_Size2_Class1, Int_ShieldGenerator_Size8_Class5_Strong
new['category'] = 'standard'
new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
if name[2] == 'overcharge':
new['name'] = standard_map[(name[1], name[2])]
else:
new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
elif name[1] in internal_map: # e.g. Int_CargoRack_Size8_Class1
new['category'] = 'internal'
@@ -205,12 +215,15 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
elif len(name) < 4 and name[1] == 'resourcesiphon': # Hack! 128066402 has no size or class.
(new['class'], new['rating']) = ('1', 'I')
elif len(name) < 4 and name[1] in ['guardianpowerdistributor', 'guardianpowerplant']: # Hack! No class.
elif len(name) < 4 and name[1] in ('guardianpowerdistributor', 'guardianpowerplant'): # Hack! No class.
(new['class'], new['rating']) = (str(name[2][4:]), 'A')
elif len(name) < 4 and name[1] in ['guardianfsdbooster']: # Hack! No class.
elif len(name) < 4 and name[1] == 'guardianfsdbooster': # Hack! No class.
(new['class'], new['rating']) = (str(name[2][4:]), 'H')
elif len(name) > 4 and name[1] == 'hyperdrive': # e.g. Int_Hyperdrive_Overcharge_Size6_Class3
(new['class'], new['rating']) = (str(name[3][-1:]), rating_map[name[4][-1:]])
else:
if len(name) < 3:
raise AssertionError(f'{name}: length < 3]')
@@ -244,7 +257,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
if not m:
print(f'No data for module {key}')
elif new['name'] == 'Frame Shift Drive':
elif new['name'] == 'Frame Shift Drive' or new['name'] == 'Frame Shift Drive (SCO)':
assert 'mass' in m and 'optmass' in m and 'maxfuel' in m and 'fuelmul' in m and 'fuelpower' in m, m
else:
+34 -24
View File
@@ -14,6 +14,7 @@ import operator
import os
import sys
import tkinter as tk
from pathlib import Path
from tkinter import ttk
from typing import Any, Mapping, MutableMapping
@@ -47,14 +48,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: Path | 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 +67,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(Path(loadfile).resolve().parent)
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 +131,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.
@@ -165,9 +170,11 @@ def load_plugins(master: tk.Tk) -> None:
def _load_internal_plugins():
internal = []
for name in sorted(os.listdir(config.internal_plugin_dir_path)):
if name.endswith('.py') and name[0] not in ['.', '_']:
if name.endswith('.py') and name[0] not in ('.', '_'):
try:
plugin = Plugin(name[:-3], os.path.join(config.internal_plugin_dir_path, name), logger)
plugin_name = name[:-3]
plugin_path = config.internal_plugin_dir_path / name
plugin = Plugin(plugin_name, plugin_path, logger)
plugin.folder = None
internal.append(plugin)
except Exception:
@@ -182,9 +189,12 @@ def _load_found_plugins():
# The intent here is to e.g. have EDMC-Overlay load before any plugins
# that depend on it.
for name in sorted(os.listdir(config.plugin_dir_path), key=lambda n: (
not os.path.isfile(os.path.join(config.plugin_dir_path, n, '__init__.py')), n.lower())):
if not os.path.isdir(os.path.join(config.plugin_dir_path, name)) or name[0] in ['.', '_']:
plugin_files = sorted(config.plugin_dir_path.iterdir(), key=lambda p: (
not (p / '__init__.py').is_file(), p.name.lower()))
for plugin_file in plugin_files:
name = plugin_file.name
if not (config.plugin_dir_path / name).is_dir() or name.startswith(('.', '_')):
pass
elif name.endswith('.disabled'):
name, discard = name.rsplit('.', 1)
@@ -192,12 +202,12 @@ def _load_found_plugins():
else:
try:
# Add plugin's folder to load path in case plugin has internal package dependencies
sys.path.append(os.path.join(config.plugin_dir_path, name))
sys.path.append(str(config.plugin_dir_path / name))
import EDMCLogging
# Create a logger for this 'found' plugin. Must be before the load.py is loaded.
plugin_logger = EDMCLogging.get_plugin_logger(name)
found.append(Plugin(name, os.path.join(config.plugin_dir_path, name, 'load.py'), plugin_logger))
found.append(Plugin(name, config.plugin_dir_path / name / 'load.py', plugin_logger))
except Exception:
PLUGINS_broken.append(Plugin(name, None, logger))
logger.exception(f'Failure loading found Plugin "{name}"')
@@ -414,7 +424,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 +439,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
+52 -29
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,12 +41,15 @@ class CoriolisConfig:
self.normal_url = ''
self.beta_url = ''
self.override_mode = ''
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)
@@ -60,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
)
@@ -81,41 +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 = 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
)
@@ -123,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
@@ -152,15 +155,35 @@ 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
if coriolis_config.override_mode not in ('beta', 'normal', 'auto'):
coriolis_config.override_mode = {
coriolis_config.override_text_old_normal: 'normal',
coriolis_config.override_text_old_beta: 'beta',
coriolis_config.override_text_old_auto: 'auto',
}.get(coriolis_config.override_mode, coriolis_config.override_mode)
# Language was seemingly changed, so we need to update the textvars
if coriolis_config.override_mode in ('beta', 'normal', 'auto'):
coriolis_config.override_textvar.set(
value={
'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, 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)
@@ -170,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':
+188 -122
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,15 +957,15 @@ 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([
('timestamp', entry['timestamp']),
('systemName', entry['StarSystem']),
('stationName', entry['StationName']),
('marketId', entry['MarketID']),
('horizons', horizons),
('modules', outfitting),
('odyssey', entry['odyssey'])
]),
'message': {
'timestamp': entry['timestamp'],
'systemName': entry['StarSystem'],
'stationName': entry['StationName'],
'marketId': entry['MarketID'],
'horizons': horizons,
'modules': outfitting,
'odyssey': entry['odyssey']
},
})
this.outfitting = (horizons, outfitting)
@@ -991,15 +991,15 @@ 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([
('timestamp', entry['timestamp']),
('systemName', entry['StarSystem']),
('stationName', entry['StationName']),
('marketId', entry['MarketID']),
('horizons', horizons),
('ships', shipyard),
('odyssey', entry['odyssey'])
]),
'message': {
'timestamp': entry['timestamp'],
'systemName': entry['StarSystem'],
'stationName': entry['StationName'],
'marketId': entry['MarketID'],
'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,
@@ -1416,15 +1416,6 @@ class EDDN:
#######################################################################
# Elisions
#######################################################################
# WORKAROUND WIP EDDN schema | 2021-10-17: This will reject with the Odyssey or Horizons flags present
if 'odyssey' in entry:
del entry['odyssey']
if 'horizons' in entry:
del entry['horizons']
# END WORKAROUND
# In case Frontier ever add any
entry = filter_localised(entry)
#######################################################################
@@ -1880,6 +1871,74 @@ class EDDN:
return None
def export_journal_dockingdenied(
self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]
) -> str | None:
"""
Send a DockingDenied to EDDN on the correct schema.
: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",
"event":"DockingDenied",
"Reason":"RestrictedAccess",
"MarketID":3706117376,
"StationName":"V7G-T1G",
"StationType":"FleetCarrier"
}
"""
#######################################################################
# Elisions
#######################################################################
# In case Frontier ever add any
entry = filter_localised(entry)
msg = {
'$schemaRef': f'https://eddn.edcd.io/schemas/dockingdenied/1{"/test" if is_beta else ""}',
'message': entry
}
this.eddn.send_message(cmdr, msg)
return None
def export_journal_dockinggranted(
self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]
) -> str | None:
"""
Send a DockingDenied to EDDN on the correct schema.
: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",
"event":"DockingGranted",
"LandingPad":41,
"MarketID":3227312896,
"StationName":"Evans Horizons",
"StationType":"Coriolis"
}
"""
#######################################################################
# Elisions
#######################################################################
# In case Frontier ever add any
entry = filter_localised(entry)
msg = {
'$schemaRef': f'https://eddn.edcd.io/schemas/dockinggranted/1{"/test" if is_beta else ""}',
'message': entry
}
this.eddn.send_message(cmdr, msg)
return None
def canonicalise(self, item: str) -> str:
"""
Canonicalise the given commodity name.
@@ -2112,7 +2171,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
@@ -2124,7 +2183,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
)
@@ -2136,7 +2195,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)
@@ -2182,14 +2241,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
@@ -2206,14 +2265,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
@@ -2251,7 +2310,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)
@@ -2327,6 +2386,12 @@ def journal_entry( # noqa: C901, CCR001
if event_name == 'fcmaterials':
return this.eddn.export_journal_fcmaterials(cmdr, is_beta, entry)
if event_name == "dockingdenied":
return this.eddn.export_journal_dockingdenied(cmdr, is_beta, entry)
if event_name == "dockinggranted":
return this.eddn.export_journal_dockinggranted(cmdr, is_beta, entry)
if event_name == 'approachsettlement':
# An `ApproachSettlement` can appear *before* `Location` if you
# logged at one. We won't have necessary augmentation data
@@ -2447,7 +2512,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)
@@ -2485,7 +2550,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)
@@ -2539,7 +2604,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)
@@ -2551,7 +2617,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)
+118 -67
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:
+183 -149
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 ''
@@ -552,25 +548,12 @@ def journal_entry( # noqa: C901, CCR001
power_defect_data = {'powerName': entry["ToPower"], 'rankValue': 1}
new_add_event('setCommanderRankPower', entry['timestamp'], power_defect_data)
elif event_name == 'Powerplay':
power_data = {'powerName': entry["Power"], 'rankValue': entry["Rank"], 'meritsValue': entry["Merits"]}
new_add_event('setCommanderRankPower', entry['timestamp'], power_data)
# 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 +704,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 +806,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 +840,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 +855,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 +895,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,18 +915,18 @@ 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 [
for (iprop, prop) in (
('missionExpiry', 'Expiry'), # Listed as optional in the docs, but always seems to be present
('starsystemNameTarget', 'DestinationSystem'),
('stationNameTarget', 'DestinationStation'),
@@ -932,7 +940,7 @@ def journal_entry( # noqa: C901, CCR001
('passengerCount', 'PassengerCount'),
('passengerIsVIP', 'PassengerVIPs'),
('passengerIsWanted', 'PassengerWanted'),
]:
):
if prop in entry:
data[iprop] = entry[prop]
@@ -946,7 +954,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 +974,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,20 +998,32 @@ 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']]
elif 'KillerName' in entry:
data['opponentName'] = entry['KillerName']
new_add_event('addCommanderCombatDeath', entry['timestamp'], data)
elif 'KillerShip' in entry:
data['opponentName'] = entry['KillerShip']
# Paranoia in case of e.g. Thargoid activity not having complete data
opponent_name_issue = 'opponentName' not in data or data['opponentName'] == ""
wing_opponent_names_issue = 'wingOpponentNames' not in data or data['wingOpponentNames'] == []
if opponent_name_issue and wing_opponent_names_issue:
logger.warning('Dropping addCommanderCombatDeath message'
'because opponentName and wingOpponentNames came out as ""')
else:
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']
@@ -1014,19 +1034,22 @@ def journal_entry( # noqa: C901, CCR001
elif 'Power' in entry:
data['opponentName'] = entry['Power']
elif 'IsThargoid' in entry and entry['IsThargoid']:
data['opponentName'] = 'Thargoid'
# Paranoia in case of e.g. Thargoid activity not having complete data
if data['opponentName'] == "":
if 'opponentName' not in data or data['opponentName'] == "":
logger.warning('Dropping addCommanderCombatInterdicted message because opponentName came out as ""')
else:
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']
@@ -1037,31 +1060,42 @@ def journal_entry( # noqa: C901, CCR001
elif 'Power' in entry:
data['opponentName'] = entry['Power']
# Shouldn't be needed here as Interdiction events can't target Thargoids (yet)
# but done just in case of future changes or so
elif 'IsThargoid' in entry and entry['IsThargoid']:
data['opponentName'] = 'Thargoid'
# Paranoia in case of e.g. Thargoid activity not having complete data
if data['opponentName'] == "":
if 'opponentName' not in data or data['opponentName'] == "":
logger.warning('Dropping addCommanderCombatInterdiction message because opponentName came out as ""')
else:
new_add_event('addCommanderCombatInterdiction', entry['timestamp'], data)
elif event_name == 'EscapeInterdiction':
data = {
'starsystemName': system,
'isPlayer': entry['IsPlayer'],
}
if 'Interdictor' in entry:
data['opponentName'] = entry['Interdictor']
elif 'Faction' in entry:
data['opponentName'] = entry['Faction']
elif 'Power' in entry:
data['opponentName'] = entry['Power']
elif 'isThargoid' in entry and entry['isThargoid']:
data['opponentName'] = 'Thargoid'
# Paranoia in case of e.g. Thargoid activity not having complete data
if entry.get('Interdictor') is None or entry['Interdictor'] == "":
logger.warning(
'Dropping addCommanderCombatInterdictionEscape message'
'because opponentName came out as ""'
)
if 'opponentName' not in data or data['opponentName'] == "":
logger.warning('Dropping addCommanderCombatInterdiction message because opponentName came out as ""')
else:
new_add_event(
'addCommanderCombatInterdictionEscape',
entry['timestamp'],
{
'starsystemName': system,
'opponentName': entry['Interdictor'],
'isPlayer': entry['IsPlayer'],
}
)
new_add_event('addCommanderCombatInterdictionEscape', entry['timestamp'], data)
elif event_name == 'PVPKill':
new_add_event(
@@ -1256,16 +1290,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])
@@ -1279,11 +1313,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']
@@ -1295,7 +1329,7 @@ def journal_entry( # noqa: C901, CCR001
# Friends
if event_name == 'Friends':
if entry['Status'] in ['Added', 'Online']:
if entry['Status'] in ('Added', 'Online'):
new_add_event(
'addCommanderFriend',
entry['timestamp'],
@@ -1305,7 +1339,7 @@ def journal_entry( # noqa: C901, CCR001
}
)
elif entry['Status'] in ['Declined', 'Lost']:
elif entry['Status'] in ('Declined', 'Lost'):
new_add_event(
'delCommanderFriend',
entry['timestamp'],
@@ -1380,7 +1414,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.
@@ -1389,13 +1423,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']
@@ -1410,20 +1444,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']
@@ -1439,11 +1473,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(
@@ -1612,7 +1646,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:
@@ -1645,7 +1679,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}'
))
@@ -1666,7 +1700,7 @@ def handle_special_events(data_event: dict[str, Any], reply_event: dict[str, Any
this.lastlocation = reply_event.get('eventData', {})
if not config.shutting_down:
this.system_link.event_generate('<<InaraLocation>>', when="tail")
elif data_event['eventName'] in ['addCommanderShip', 'setCommanderShip']:
elif data_event['eventName'] in ('addCommanderShip', 'setCommanderShip'):
this.lastship = reply_event.get('eventData', {})
if not config.shutting_down:
this.system_link.event_generate('<<InaraShip>>', when="tail")
+250 -279
View File
File diff suppressed because it is too large. Load diff
+15 -83
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
@@ -134,6 +76,7 @@ elif (config.auth_force_edmc_protocol
ATOM, BOOL, DWORD, HBRUSH, HGLOBAL, HICON, HINSTANCE, HMENU, HWND, INT, LPARAM, LPCWSTR, LPMSG, LPVOID, LPWSTR,
MSG, UINT, WPARAM
)
import win32gui
class WNDCLASS(Structure):
"""
@@ -163,14 +106,6 @@ elif (config.auth_force_edmc_protocol
CreateWindowExW.restype = HWND
RegisterClassW = windll.user32.RegisterClassW
RegisterClassW.argtypes = [POINTER(WNDCLASS)]
# DefWindowProcW
# Ref: <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowprocw>
# LRESULT DefWindowProcW([in] HWND hWnd,[in] UINT Msg,[in] WPARAM wParam,[in] LPARAM lParam);
# As per example at <https://docs.python.org/3/library/ctypes.html#ctypes.WINFUNCTYPE>
prototype = WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM)
paramflags = (1, "hWnd"), (1, "Msg"), (1, "wParam"), (1, "lParam")
DefWindowProcW = prototype(("DefWindowProcW", windll.user32), paramflags)
GetParent = windll.user32.GetParent
SetForegroundWindow = windll.user32.SetForegroundWindow
@@ -229,7 +164,7 @@ elif (config.auth_force_edmc_protocol
if message != WM_DDE_INITIATE:
# Not a DDE init message, bail and tell windows to do the default
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowproca?redirectedfrom=MSDN
return DefWindowProcW(hwnd, message, wParam, lParam)
return win32gui.DefWindowProc(hwnd, message, wParam, lParam)
service = create_unicode_buffer(256)
topic = create_unicode_buffer(256)
@@ -245,11 +180,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:
@@ -441,7 +376,7 @@ else: # Linux / Run from source
if self.parse():
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(self._generate_auth_response().encode('utf-8'))
self.wfile.write(self._generate_auth_response().encode())
else:
self.send_response(404)
self.end_headers()
@@ -480,12 +415,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
-3
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'
+17 -23
View File
@@ -1,49 +1,43 @@
# So that you don't get warnings like:
# Using legacy 'setup.py install' for flake8-annotations-coverage, since package 'wheel' is not installed.
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.0.2
setuptools==75.8.0
# Static analysis tools
flake8==6.1.0
flake8==7.1.1
flake8-annotations-coverage==0.0.6
flake8-cognitive-complexity==0.1.0
flake8-comprehensions==3.14.0
flake8-comprehensions==3.15.0
flake8-docstrings==1.7.0
flake8-json==23.7.0
flake8-noqa==1.3.2
flake8-json==24.4.0
flake8-noqa==1.4.0
flake8-polyfill==1.0.2
flake8-use-fstring==1.4
mypy==1.7.1
pep8-naming==0.13.3
safety==2.3.5
types-requests==2.31.0.10
types-pkg-resources==0.1.3
mypy==1.14.1
pep8-naming==0.14.1
safety==3.2.14
types-requests==2.32.0.20241016
types-setuptools==75.8.0.20250210
# Code formatting tools
autopep8==2.0.4
autopep8==2.3.2
# Git pre-commit checking
pre-commit==3.5.0
pre-commit==3.8.0
# HTML changelogs
grip==4.6.2
mistune==3.1.1
# Packaging
# We only need py2exe on windows.
py2exe==0.13.0.1; sys_platform == 'win32'
py2exe==0.13.0.2; sys_platform == 'win32'
# Testing
pytest==7.4.3
pytest-cov==4.1.0 # Pytest code coverage support
coverage[toml]==7.3.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
pytest==8.3.2
pytest-cov==6.0.0 # Pytest code coverage support
coverage[toml]==7.6.1 # 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'
# All of the normal requirements
+7 -13
View File
@@ -1,14 +1,8 @@
certifi==2023.11.17
requests==2.31.0
# requests depends on this now ?
charset-normalizer==2.1.1
watchdog==3.0.0
# Commented out because this doesn't package well with py2exe
infi.systray==0.1.12; sys_platform == 'win32'
# argh==0.26.2 watchdog dep
# pyyaml==5.3.1 watchdog dep
requests==2.32.3
pillow==11.1.0
watchdog==6.0.0
simplesystray==0.1.0; sys_platform == 'win32'
semantic-version==2.10.0
# Base requirement for MacOS
pyobjc; sys_platform == 'darwin'
# For manipulating folder permissions and the like.
pywin32==308; sys_platform == 'win32'
psutil==6.1.1
File renamed without changes.
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff. Show more