From 27fe43bf8ca5db0f69b42a6b50c85fbd1c008e9f Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 9 Aug 2021 04:37:23 +0300 Subject: [PATCH 001/316] WIP: implement journal_entry_cqc() journal_entry_cqc function in plugin's api allow plugins to recieve journal events when player are in CQC --- EDMarketConnector.py | 13 ++++++++++++- monitor.py | 7 ++++++- plug.py | 23 +++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index d6eaa750..9a076a73 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -872,7 +872,8 @@ class AppWindow(object): play_bad = False err: Optional[str] = None - if not monitor.cmdr or not monitor.mode or monitor.state['Captain'] or not monitor.system: + if not monitor.cmdr or not monitor.mode or monitor.state['Captain'] or not monitor.system or \ + monitor.mode == 'CQC': return # In CQC or on crew - do nothing if companion.session.state == companion.Session.STATE_AUTH: @@ -1171,6 +1172,15 @@ class AppWindow(object): config.set('cmdrs', config.get_list('cmdrs', default=[]) + [monitor.cmdr]) self.login() + if monitor.mode == 'CQC': + err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, entry, monitor.state) + if err: + self.status['text'] = err + if not config.get_int('hotkey_mute'): + hotkeymgr.play_bad() + + return # in CQC + if not entry['event'] or not monitor.mode: # logger.trace('Startup or in CQC, returning') return # Startup or in CQC @@ -1335,6 +1345,7 @@ class AppWindow(object): self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window self.button['state'] = self.theme_button['state'] = (monitor.cmdr and monitor.mode and + # and monitor.mode == 'CQC' and not monitor.state['Captain'] and monitor.system and tk.NORMAL or tk.DISABLED) diff --git a/monitor.py b/monitor.py index 01a614e7..98d152b5 100644 --- a/monitor.py +++ b/monitor.py @@ -494,6 +494,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below entry['timestamp'] # we expect this to exist # TODO: replace with assert? or an if key in check event_type = entry['event'].lower() + # logger.debug(f'Monitor event: {entry["event"]}') if event_type == 'fileheader': self.live = False @@ -529,7 +530,11 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # Odyssey: bool self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) - self.mode = entry.get('GameMode') + if not entry.get('Ship'): # TODO: Test with loading to CQC right from main game + logger.debug(f'loadgame to cqc: {entry}') + self.mode = 'CQC' + else: + self.mode = entry.get('GameMode') self.group = entry.get('Group') self.planet = None self.system = None diff --git a/plug.py b/plug.py index af4a50ac..ec31ad54 100644 --- a/plug.py +++ b/plug.py @@ -312,6 +312,29 @@ def notify_newdata(data, is_beta): return error +def notify_journal_entry_cqc(cmdr, is_beta, entry, state): + """ + Send a journal entry to each plugin. + :param cmdr: The Cmdr name, or None if not yet known + :param entry: The journal entry as a dictionary + :param state: A dictionary containing info about the Cmdr, current ship and cargo + :param is_beta: whether the player is in a Beta universe. + :returns: Error message from the first plugin that returns one (if any) + """ + + error = None + for plugin in PLUGINS: + journal_entry = plugin._get_func('journal_entry_cqc') + if journal_entry: + try: + # Pass a copy of the journal entry in case the callee modifies it + newerror = journal_entry(cmdr, is_beta, dict(entry), dict(state)) + error = error or newerror + except Exception as e: + logger.exception(f'Plugin "{plugin.name}" failed') + return error + + def show_error(err): """ Display an error message in the status line of the main window. From 291fbf2908e0f7419769a7727ee0a79cf58a9342 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 9 Aug 2021 15:59:30 +0300 Subject: [PATCH 002/316] Use both Ship and GameMode fields of LoadGame event to detect CQC And freeze update button when in CQC --- EDMarketConnector.py | 2 +- monitor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 9a076a73..49e9fb80 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1345,7 +1345,7 @@ class AppWindow(object): self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window self.button['state'] = self.theme_button['state'] = (monitor.cmdr and monitor.mode and - # and monitor.mode == 'CQC' and + monitor.mode == 'CQC' and not monitor.state['Captain'] and monitor.system and tk.NORMAL or tk.DISABLED) diff --git a/monitor.py b/monitor.py index 98d152b5..4c94cecf 100644 --- a/monitor.py +++ b/monitor.py @@ -530,7 +530,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # Odyssey: bool self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) - if not entry.get('Ship'): # TODO: Test with loading to CQC right from main game + if not entry.get('Ship') and not entry.get('GameMode'): logger.debug(f'loadgame to cqc: {entry}') self.mode = 'CQC' else: From 34d6aeccafa26746c799b9d403a45b0d2f58bd4e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 14:02:41 +0100 Subject: [PATCH 003/316] github/windows-build: Create a post in "Announcements" for any Release --- .github/workflows/windows-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 0c04cd6f..e1041864 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -65,6 +65,7 @@ jobs: token: "${{secrets.GITHUB_TOKEN}}" draft: true prerelease: true + discussion_category_name: "Announcement" files: | ./EDMarketConnector_win*.msi ./hashes.sum From d7dd8c899097d5e3f211655c8193ebcfdafa78eb Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 9 Aug 2021 17:15:54 +0300 Subject: [PATCH 004/316] Be more paranoid with GameMode and CQC --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 4c94cecf..02c1ed7f 100644 --- a/monitor.py +++ b/monitor.py @@ -530,7 +530,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # Odyssey: bool self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) - if not entry.get('Ship') and not entry.get('GameMode'): + if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode').lower() == 'CQC': logger.debug(f'loadgame to cqc: {entry}') self.mode = 'CQC' else: From 11e94f18525c24a895238a0901a2073e4052833f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:16:46 +0100 Subject: [PATCH 005/316] Licenses: Add python.org license --- docs/Licenses/python.org/LICENSE.txt | 649 +++++++++++++++++++++++++++ 1 file changed, 649 insertions(+) create mode 100644 docs/Licenses/python.org/LICENSE.txt diff --git a/docs/Licenses/python.org/LICENSE.txt b/docs/Licenses/python.org/LICENSE.txt new file mode 100644 index 00000000..c0a72782 --- /dev/null +++ b/docs/Licenses/python.org/LICENSE.txt @@ -0,0 +1,649 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +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 appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM 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. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +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. + + + +Additional Conditions for this Windows binary build +--------------------------------------------------- + +This program is linked with and uses Microsoft Distributable Code, +copyrighted by Microsoft Corporation. The Microsoft Distributable Code +is embedded in each .exe, .dll and .pyd file as a result of running +the code through a linker. + +If you further distribute programs that include the Microsoft +Distributable Code, you must comply with the restrictions on +distribution specified by Microsoft. In particular, you must require +distributors and external end users to agree to terms that protect the +Microsoft Distributable Code at least as much as Microsoft's own +requirements for the Distributable Code. See Microsoft's documentation +(included in its developer tools and on its website at microsoft.com) +for specific details. + +Redistribution of the Windows binary build of the Python interpreter +complies with this agreement, provided that you do not: + +- alter any copyright, trademark or patent notice in Microsoft's +Distributable Code; + +- use Microsoft's trademarks in your programs' names or in a way that +suggests your programs come from or are endorsed by Microsoft; + +- distribute Microsoft's Distributable Code to run on a platform other +than Microsoft operating systems, run-time technologies or application +platforms; or + +- include Microsoft Distributable Code in malicious, deceptive or +unlawful programs. + +These restrictions apply only to the Microsoft Distributable Code as +defined above, not to Python itself or any programs running on the +Python interpreter. The redistribution of the Python interpreter and +libraries is governed by the Python Software License included with this +file, or by other licenses as marked. + + + +-------------------------------------------------------------------------- + +This program, "bzip2", the associated library "libbzip2", and all +documentation, are copyright (C) 1996-2010 Julian R Seward. All +rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + +4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + +Julian Seward, jseward@bzip.org +bzip2/libbzip2 version 1.0.6 of 6 September 2010 + +-------------------------------------------------------------------------- + + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a double license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED 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 OpenSSL PROJECT OR + * ITS 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. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + +libffi - Copyright (c) 1996-2014 Anthony Green, Red Hat, Inc and others. +See source files for details. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +``Software''), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This software is copyrighted by the Regents of the University of +California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState +Corporation and other parties. The following terms apply to all files +associated with the software unless explicitly disclaimed in +individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7014 (b) (3) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +This software is copyrighted by the Regents of the University of +California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState +Corporation, Apple Inc. and other parties. The following terms apply to +all files associated with the software unless explicitly disclaimed in +individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (b) (3) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +Copyright (c) 1993-1999 Ioi Kim Lam. +Copyright (c) 2000-2001 Tix Project Group. +Copyright (c) 2004 ActiveState + +This software is copyrighted by the above entities +and other parties. The following terms apply to all files associated +with the software unless explicitly disclaimed in individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +---------------------------------------------------------------------- + +Parts of this software are based on the Tcl/Tk software copyrighted by +the Regents of the University of California, Sun Microsystems, Inc., +and other parties. The original license terms of the Tcl/Tk software +distribution is included in the file docs/license.tcltk. + +Parts of this software are based on the HTML Library software +copyrighted by Sun Microsystems, Inc. The original license terms of +the HTML Library software distribution is included in the file +docs/license.html_lib. From 668af1c3cf3262194e821c16ec23a8f5a765fa41 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:19:40 +0100 Subject: [PATCH 006/316] Licenses: Add py2exe license files --- docs/Licenses/py2exe/LICENSE.txt | 1 + docs/Licenses/py2exe/MIT-License.txt | 21 ++ docs/Licenses/py2exe/MPL2-License.txt | 373 ++++++++++++++++++++++++++ 3 files changed, 395 insertions(+) create mode 100644 docs/Licenses/py2exe/LICENSE.txt create mode 100644 docs/Licenses/py2exe/MIT-License.txt create mode 100644 docs/Licenses/py2exe/MPL2-License.txt diff --git a/docs/Licenses/py2exe/LICENSE.txt b/docs/Licenses/py2exe/LICENSE.txt new file mode 100644 index 00000000..13187080 --- /dev/null +++ b/docs/Licenses/py2exe/LICENSE.txt @@ -0,0 +1 @@ +You are being redirected. diff --git a/docs/Licenses/py2exe/MIT-License.txt b/docs/Licenses/py2exe/MIT-License.txt new file mode 100644 index 00000000..ae75921a --- /dev/null +++ b/docs/Licenses/py2exe/MIT-License.txt @@ -0,0 +1,21 @@ +Copyright (c) 2000-2021 Thomas Heller, Mark Hammond, Jimmy Retzlaff, +Alberto Sottile + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/Licenses/py2exe/MPL2-License.txt b/docs/Licenses/py2exe/MPL2-License.txt new file mode 100644 index 00000000..14e2f777 --- /dev/null +++ b/docs/Licenses/py2exe/MPL2-License.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. From 1b0438e06ee944bde46dbeac3c02d23cf2697c78 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:21:27 +0100 Subject: [PATCH 007/316] Licenses: WinSparkle --- docs/Licenses/WinSparkle/COPYING | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/Licenses/WinSparkle/COPYING diff --git a/docs/Licenses/WinSparkle/COPYING b/docs/Licenses/WinSparkle/COPYING new file mode 100644 index 00000000..97a88c44 --- /dev/null +++ b/docs/Licenses/WinSparkle/COPYING @@ -0,0 +1 @@ +You are being redirected. From 86355a03329b45d69a70e1a0492c2bc2b0e5d046 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:22:50 +0100 Subject: [PATCH 008/316] Licenses: WiX Toolset --- docs/Licenses/WiXtoolset/LICENSE.TXT | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/Licenses/WiXtoolset/LICENSE.TXT diff --git a/docs/Licenses/WiXtoolset/LICENSE.TXT b/docs/Licenses/WiXtoolset/LICENSE.TXT new file mode 100644 index 00000000..d4d316ef --- /dev/null +++ b/docs/Licenses/WiXtoolset/LICENSE.TXT @@ -0,0 +1,28 @@ +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. From ad4304a18454246431e8fd15322d4f7544434caa Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 9 Aug 2021 17:23:57 +0300 Subject: [PATCH 009/316] Be more paranoid with empty events when in CQC --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 49e9fb80..e8c567da 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1172,7 +1172,7 @@ class AppWindow(object): config.set('cmdrs', config.get_list('cmdrs', default=[]) + [monitor.cmdr]) self.login() - if monitor.mode == 'CQC': + if monitor.mode == 'CQC' and entry['event']: err = plug.notify_journal_entry_cqc(monitor.cmdr, monitor.is_beta, entry, monitor.state) if err: self.status['text'] = err From bade23a167d604852762f4733ebe53b247dcb396 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:25:44 +0100 Subject: [PATCH 010/316] Licenses: Windows SDK Kit --- docs/Licenses/Windows-SDK/sdk_license.rtf | 1148 +++++++++++++++++ .../Windows-SDK/sdk_third_party_notices.rtf | 325 +++++ 2 files changed, 1473 insertions(+) create mode 100644 docs/Licenses/Windows-SDK/sdk_license.rtf create mode 100644 docs/Licenses/Windows-SDK/sdk_third_party_notices.rtf diff --git a/docs/Licenses/Windows-SDK/sdk_license.rtf b/docs/Licenses/Windows-SDK/sdk_license.rtf new file mode 100644 index 00000000..e8d28871 --- /dev/null +++ b/docs/Licenses/Windows-SDK/sdk_license.rtf @@ -0,0 +1,1148 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff43\deff0\stshfdbch11\stshfloch0\stshfhich0\stshfbi0\deflang1033\deflangfe1033\themelang1033\themelangfe2052\themelangcs1025{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times};} +{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New{\*\falt Arial};}{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol{\*\falt Bookshelf Symbol 3};} +{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings{\*\falt Symbol};}{\f11\fbidi \fmodern\fcharset128\fprq1{\*\panose 02020609040205080304}MS Mincho{\*\falt ?l?r ??\'81\'66c};} +{\f13\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}SimSun{\*\falt ???????\'a1\'ec????};}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math{\*\falt Calisto MT};} +{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri{\*\falt Arial Rounded MT Bold};}{\f43\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Tahoma{\*\falt ?? ??};} +{\f44\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0603020202020204}Trebuchet MS{\*\falt Univers};}{\f45\fbidi \fnil\fcharset134\fprq2{\*\panose 00000000000000000000}@SimSun;} +{\f46\fbidi \fmodern\fcharset128\fprq1{\*\panose 00000000000000000000}@MS Mincho{\*\falt @MS Gothic};}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times};} +{\fdbmajor\f31501\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}SimSun{\*\falt ???????\'a1\'ec????};}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;} +{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times};}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times};} +{\fdbminor\f31505\fbidi \fnil\fcharset134\fprq2{\*\panose 02010600030101010101}SimSun{\*\falt ???????\'a1\'ec????};}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri{\*\falt Arial Rounded MT Bold};} +{\fbiminor\f31507\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}{\f61\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times};}{\f62\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times};} +{\f64\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times};}{\f65\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times};}{\f66\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times};} +{\f67\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times};}{\f68\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times};}{\f69\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times};} +{\f81\fbidi \fmodern\fcharset238\fprq1 Courier New CE{\*\falt Arial};}{\f82\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr{\*\falt Arial};}{\f84\fbidi \fmodern\fcharset161\fprq1 Courier New Greek{\*\falt Arial};} +{\f85\fbidi \fmodern\fcharset162\fprq1 Courier New Tur{\*\falt Arial};}{\f86\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew){\*\falt Arial};}{\f87\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic){\*\falt Arial};} +{\f88\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic{\*\falt Arial};}{\f89\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese){\*\falt Arial};}{\f173\fbidi \fmodern\fcharset0\fprq1 MS Mincho Western{\*\falt ?l?r ??\'81\'66c};} +{\f171\fbidi \fmodern\fcharset238\fprq1 MS Mincho CE{\*\falt ?l?r ??\'81\'66c};}{\f172\fbidi \fmodern\fcharset204\fprq1 MS Mincho Cyr{\*\falt ?l?r ??\'81\'66c};}{\f174\fbidi \fmodern\fcharset161\fprq1 MS Mincho Greek{\*\falt ?l?r ??\'81\'66c};} +{\f175\fbidi \fmodern\fcharset162\fprq1 MS Mincho Tur{\*\falt ?l?r ??\'81\'66c};}{\f178\fbidi \fmodern\fcharset186\fprq1 MS Mincho Baltic{\*\falt ?l?r ??\'81\'66c};}{\f193\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt ???????\'a1\'ec????};} +{\f401\fbidi \froman\fcharset238\fprq2 Cambria Math CE{\*\falt Calisto MT};}{\f402\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr{\*\falt Calisto MT};}{\f404\fbidi \froman\fcharset161\fprq2 Cambria Math Greek{\*\falt Calisto MT};} +{\f405\fbidi \froman\fcharset162\fprq2 Cambria Math Tur{\*\falt Calisto MT};}{\f408\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic{\*\falt Calisto MT};}{\f409\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese){\*\falt Calisto MT};} +{\f431\fbidi \fswiss\fcharset238\fprq2 Calibri CE{\*\falt Arial Rounded MT Bold};}{\f432\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr{\*\falt Arial Rounded MT Bold};}{\f434\fbidi \fswiss\fcharset161\fprq2 Calibri Greek{\*\falt Arial Rounded MT Bold};} +{\f435\fbidi \fswiss\fcharset162\fprq2 Calibri Tur{\*\falt Arial Rounded MT Bold};}{\f436\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew){\*\falt Arial Rounded MT Bold};} +{\f437\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic){\*\falt Arial Rounded MT Bold};}{\f438\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic{\*\falt Arial Rounded MT Bold};} +{\f439\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese){\*\falt Arial Rounded MT Bold};}{\f491\fbidi \fswiss\fcharset238\fprq2 Tahoma CE{\*\falt ?? ??};}{\f492\fbidi \fswiss\fcharset204\fprq2 Tahoma Cyr{\*\falt ?? ??};} +{\f494\fbidi \fswiss\fcharset161\fprq2 Tahoma Greek{\*\falt ?? ??};}{\f495\fbidi \fswiss\fcharset162\fprq2 Tahoma Tur{\*\falt ?? ??};}{\f496\fbidi \fswiss\fcharset177\fprq2 Tahoma (Hebrew){\*\falt ?? ??};} +{\f497\fbidi \fswiss\fcharset178\fprq2 Tahoma (Arabic){\*\falt ?? ??};}{\f498\fbidi \fswiss\fcharset186\fprq2 Tahoma Baltic{\*\falt ?? ??};}{\f499\fbidi \fswiss\fcharset163\fprq2 Tahoma (Vietnamese){\*\falt ?? ??};} +{\f500\fbidi \fswiss\fcharset222\fprq2 Tahoma (Thai){\*\falt ?? ??};}{\f501\fbidi \fswiss\fcharset238\fprq2 Trebuchet MS CE{\*\falt Univers};}{\f502\fbidi \fswiss\fcharset204\fprq2 Trebuchet MS Cyr{\*\falt Univers};} +{\f504\fbidi \fswiss\fcharset161\fprq2 Trebuchet MS Greek{\*\falt Univers};}{\f505\fbidi \fswiss\fcharset162\fprq2 Trebuchet MS Tur{\*\falt Univers};}{\f508\fbidi \fswiss\fcharset186\fprq2 Trebuchet MS Baltic{\*\falt Univers};} +{\f513\fbidi \fnil\fcharset0\fprq2 @SimSun Western;}{\f523\fbidi \fmodern\fcharset0\fprq1 @MS Mincho Western{\*\falt @MS Gothic};}{\f521\fbidi \fmodern\fcharset238\fprq1 @MS Mincho CE{\*\falt @MS Gothic};} +{\f522\fbidi \fmodern\fcharset204\fprq1 @MS Mincho Cyr{\*\falt @MS Gothic};}{\f524\fbidi \fmodern\fcharset161\fprq1 @MS Mincho Greek{\*\falt @MS Gothic};}{\f525\fbidi \fmodern\fcharset162\fprq1 @MS Mincho Tur{\*\falt @MS Gothic};} +{\f528\fbidi \fmodern\fcharset186\fprq1 @MS Mincho Baltic{\*\falt @MS Gothic};}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times};}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times};} +{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times};}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times};} +{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times};}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times};} +{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times};}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times};} +{\fdbmajor\f31520\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt ???????\'a1\'ec????};}{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;} +{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;}{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;} +{\fhimajor\f31536\fbidi \froman\fcharset163\fprq2 Cambria (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times};}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times};} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times};}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times};} +{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times};}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times};} +{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times};}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times};} +{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times};}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times};} +{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times};}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times};} +{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times};}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times};} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times};}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese){\*\falt Times};} +{\fdbminor\f31560\fbidi \fnil\fcharset0\fprq2 SimSun Western{\*\falt ???????\'a1\'ec????};}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE{\*\falt Arial Rounded MT Bold};} +{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr{\*\falt Arial Rounded MT Bold};}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek{\*\falt Arial Rounded MT Bold};} +{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur{\*\falt Arial Rounded MT Bold};}{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew){\*\falt Arial Rounded MT Bold};} +{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic){\*\falt Arial Rounded MT Bold};}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic{\*\falt Arial Rounded MT Bold};} +{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese){\*\falt Arial Rounded MT Bold};}{\fbiminor\f31578\fbidi \fswiss\fcharset238\fprq2 Arial CE;}{\fbiminor\f31579\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;} +{\fbiminor\f31581\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\fbiminor\f31582\fbidi \fswiss\fcharset162\fprq2 Arial Tur;}{\fbiminor\f31583\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);} +{\fbiminor\f31584\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);}{\fbiminor\f31585\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;}{\fbiminor\f31586\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255; +\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0; +\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\cfollowedhyperlink\ctint255\cshade255\red128\green0\blue128;\red43\green87\blue154;\red230\green230\blue230;\red96\green94\blue92; +\red225\green223\blue221;\red0\green0\blue102;\ctextone\ctint255\cshade255\red0\green0\blue0;}{\*\defchp \fs22\dbch\af11 }{\*\defpap \ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 } +\noqfpromote {\stylesheet{\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \snext0 \sautoupd \sqformat \spriority0 \styrsid14370462 Normal;}{\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext1 \slink15 \sqformat heading 1;}{\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext2 \slink16 \sqformat heading 2;}{\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext3 \slink17 \sqformat heading 3;}{\s4\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext4 \slink18 \sqformat heading 4;}{\s5\ql \fi-357\li1792\ri0\sb120\sa120\widctlpar +\tx1792\jclisttab\tx2155\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl4\outlinelevel4\adjustright\rin0\lin1792\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext5 \slink19 \sqformat heading 5;}{\s6\ql \fi-357\li2149\ri0\sb120\sa120\widctlpar\jclisttab\tx2152\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl5\outlinelevel5\adjustright\rin0\lin2149\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext6 \slink20 \sqformat heading 6;}{\s7\ql \fi-357\li2506\ri0\sb120\sa120\widctlpar +\jclisttab\tx2509\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl6\outlinelevel6\adjustright\rin0\lin2506\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext7 \slink21 \sqformat heading 7;}{\s8\ql \fi-357\li2863\ri0\sb120\sa120\widctlpar\jclisttab\tx2866\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl7\outlinelevel7\adjustright\rin0\lin2863\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext8 \slink22 \sqformat heading 8;}{\s9\ql \fi-358\li3221\ri0\sb120\sa120\widctlpar +\jclisttab\tx3223\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl8\outlinelevel8\adjustright\rin0\lin3221\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext9 \slink23 \sqformat heading 9;}{\*\cs10 \additive \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa200\sl276\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f0\hich\af0\dbch\af11\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\* +\cs15 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 \sbasedon10 \slink1 \slocked Heading 1 Char;}{\*\cs16 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 \sbasedon10 \slink2 \slocked Heading 2 Char;}{\*\cs17 \additive +\rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink3 \slocked Heading 3 Char;}{\*\cs18 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink4 \slocked Heading 4 Char;}{\*\cs19 \additive \rtlch\fcs1 \af43\afs19 +\ltrch\fcs0 \f43\fs19 \sbasedon10 \slink5 \slocked Heading 5 Char;}{\*\cs20 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink6 \slocked Heading 6 Char;}{\*\cs21 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 +\sbasedon10 \slink7 \slocked Heading 7 Char;}{\*\cs22 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink8 \slocked Heading 8 Char;}{\*\cs23 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink9 \slocked +Heading 9 Char;}{\s24\ql \li357\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext24 Body 1;}{\s25\ql \li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext25 Body 2;}{\s26\ql \li1077\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext26 Body 3;}{ +\s27\ql \li1435\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext27 Body 4;}{\s28\ql \li1803\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1803\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext28 Body 5;}{\s29\ql \li2160\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin2160\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext29 Body 6;}{ +\s30\ql \li2506\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin2506\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext30 Body 7;}{\s31\ql \li2863\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin2863\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext31 Body 8;}{\s32\ql \li3221\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin3221\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext32 Body 9;}{\s33\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls1\adjustright\rin0\lin357\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext33 Bullet 1;}{ +\s34\ql \fi-363\li720\ri0\sb120\sa120\widctlpar\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext34 Bullet 2;}{\s35\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\jclisttab\tx1080\wrapdefault\aspalpha\aspnum\faauto\ls3\adjustright\rin0\lin1077\itap0 +\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext35 \slink87 Bullet 3;}{\s36\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext36 Bullet 4;}{ +\s37\ql \fi-357\li1792\ri0\sb120\sa120\widctlpar\jclisttab\tx1795\wrapdefault\aspalpha\aspnum\faauto\ls5\adjustright\rin0\lin1792\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext37 Bullet 5;}{\s38\ql \fi-357\li2149\ri0\sb120\sa120\widctlpar\jclisttab\tx2152\wrapdefault\aspalpha\aspnum\faauto\ls6\adjustright\rin0\lin2149\itap0 +\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext38 Bullet 6;}{\s39\ql \fi-357\li2506\ri0\sb120\sa120\widctlpar +\jclisttab\tx2509\wrapdefault\aspalpha\aspnum\faauto\ls7\adjustright\rin0\lin2506\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext39 Bullet 7;}{ +\s40\ql \fi-357\li2863\ri0\sb120\sa120\widctlpar\jclisttab\tx2866\wrapdefault\aspalpha\aspnum\faauto\ls8\adjustright\rin0\lin2863\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext40 Bullet 8;}{\s41\ql \fi-358\li3221\ri0\sb120\sa120\widctlpar\jclisttab\tx3223\wrapdefault\aspalpha\aspnum\faauto\ls9\adjustright\rin0\lin3221\itap0 +\rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon32 \snext41 Bullet 9;}{ +\s42\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 \b\fs28\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext0 Heading EULA;}{\s43\ql \li90\ri0\sb120\sa120\widctlpar\brdrb\brdrs\brdrw10\brsp20 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \ab\af43\afs28\alang1025 \ltrch\fcs0 +\b\fs28\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 Heading Software Title;}{\s44\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 +\ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext44 Preamble;}{\s45\ql \li90\ri0\sb120\sa120\widctlpar\brdrb\brdrs\brdrw10\brsp20 +\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext1 Preamble Border;}{ +\s46\qc \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext46 Heading Warranty;}{\s47\ql \fi-360\li360\ri0\sb120\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls11\outlinelevel0\adjustright\rin0\lin360\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 Heading 1 Warranty;}{\s48\ql \fi-360\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls11\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext0 Heading 2 Warranty;}{\s49\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls10\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon3 \snext49 \slink107 Heading 3 Bold;}{\s50\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon36 \snext50 +Bullet 4 Underline;}{\s51\ql \li0\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon35 \snext51 Bullet 3 Underline;}{\s52\ql \li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon25 \snext52 Body 2 Underline;}{ +\s53\ql \li1077\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon26 \snext53 Body 3 Underline;}{\s54\ql \li90\ri0\sb120\sa120\sl480\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext54 \slink55 Body Text Indent;}{\*\cs55 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink54 \slocked \ssemihidden +Body Text Indent Char;}{\s56\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \ai\af43\afs19\alang1025 \ltrch\fcs0 +\i\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon36 \snext56 Bullet 4 Italics;}{\*\cs57 \additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 Body 2 Char;}{\* +\cs58 \additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 Body 3 Char;}{\*\cs59 \additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 Body 4 Char;}{\*\cs60 +\additive \rtlch\fcs1 \af43 \ltrch\fcs0 \f43\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 Body 1 Char;}{\s61\ql \li90\ri0\sb120\sa120\widctlpar\brdrt\brdrs\brdrw10\brsp20 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 +\rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon44 \snext61 Preamble Border Above;}{ +\s62\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext62 \slink63 \ssemihidden footnote text;}{\*\cs63 \additive \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \f43\fs20 \sbasedon10 \slink62 \slocked \ssemihidden Footnote Text Char;}{\*\cs64 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \super +\sbasedon10 \ssemihidden footnote reference;}{\s65\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext65 \slink66 \ssemihidden endnote text;}{\*\cs66 \additive \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \f43\fs20 \sbasedon10 \slink65 \slocked \ssemihidden +Endnote Text Char;}{\*\cs67 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \super \sbasedon10 \ssemihidden endnote reference;}{\s68\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext68 \slink69 \ssemihidden annotation text;}{\*\cs69 \additive \rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \f43\fs20 +\sbasedon10 \slink68 \slocked \ssemihidden Comment Text Char;}{\*\cs70 \additive \rtlch\fcs1 \af0\afs16 \ltrch\fcs0 \fs16 \sbasedon10 \ssemihidden annotation reference;}{\s71\ql \li90\ri0\sa160\sl-240\slmult0 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext71 Char;}{ +\s72\ql \li90\ri0\sa160\sl-240\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext72 Char Char Char Char;}{\*\cs73 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf2 \sbasedon10 Hyperlink,Char Char7;}{\s74\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 +\af43\afs16\alang1025 \ltrch\fcs0 \fs16\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext74 \slink75 \ssemihidden Balloon Text;}{\*\cs75 \additive \rtlch\fcs1 \af43\afs16 \ltrch\fcs0 \f43\fs16 +\sbasedon10 \slink74 \slocked \ssemihidden Balloon Text Char;}{\*\cs76 \additive \rtlch\fcs1 \ab\af44 \ltrch\fcs0 \b\f44\lang1033\langfe1033\langnp1033\langfenp1033 \sbasedon10 Heading 2 Char1;}{\*\cs77 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \sbasedon10 +page number;}{\s78\ql \li90\ri0\sa160\sl-240\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext78 Char Char Char Char1;}{\s79\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ab\af43\afs19\alang1025 +\ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \snext0 \slink109 Body 0 Bold;}{\s80\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs19\alang1025 +\ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \snext0 Body 0;}{\s81\ql \li90\ri0\sb120\sa120\widctlpar\tqc\tx4320\tqr\tx8640\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext81 \slink82 header;}{\*\cs82 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink81 \slocked +Header Char;}{\s83\ql \li90\ri0\sb120\sa120\widctlpar\tqc\tx4320\tqr\tx8640\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext83 \slink84 footer;}{\*\cs84 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink83 \slocked Footer Char;}{ +\s85\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \ab\af43\afs20\alang1025 \ltrch\fcs0 \b\fs20\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon68 \snext68 \slink86 \ssemihidden \sunhideused annotation subject;}{\*\cs86 \additive \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20 \sbasedon69 \slink85 \slocked \ssemihidden Comment Subject Char;}{\*\cs87 \additive \rtlch\fcs1 \af43\afs19 +\ltrch\fcs0 \f43\fs19 \sbasedon10 \slink35 \slocked Bullet 3 Char1;}{\s88\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\jclisttab\tx1080\wrapdefault\aspalpha\aspnum\faauto\ls3\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon35 \snext88 Bullet 3 Underlined;}{\*\cs89 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\langnp1033\langfenp1033 \sbasedon10 Char Char;}{\s90\ql \li0\ri0\sl-240\slmult0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af43\afs20\alang1025 \ltrch\fcs0 +\fs18\lang1033\langfe1033\loch\f44\hich\af44\dbch\af11\cgrid\langnp1033\langfenp1033 \snext90 \spriority0 AdditionalSoftware;}{\*\cs91 \additive \rtlch\fcs1 \af43\afs24\alang1025 \ltrch\fcs0 \b\f44\fs24\lang1033\langfe1033\langnp1033\langfenp1033 +\sbasedon10 \spriority0 Char Char1;}{\s92\ql \fi-358\li1435\ri0\sb120\sa120\widctlpar\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin1435\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\ul\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon36 \snext92 \spriority0 Bullet 4 Underlined;}{\s93\ql \fi-360\li360\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls13\adjustright\rin0\lin360\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext93 \spriority0 +Heading French Warranty;}{\*\cs94 \additive \f43\lang1033\langfe0\langnp1033\langfenp0 \slocked Body 3 Char Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car Car;}{\*\cs95 \additive +\f2\cf15\lang1024\langfe1024\noproof tw4winExternal;}{\*\cs96 \additive \v\f2\fs24\cf12\sub tw4winMark;}{\*\cs97 \additive \b\f43 Preamble Char;}{\*\cs98 \additive \f2\fs40\cf4 tw4winError;}{\*\cs99 \additive \cf2 tw4winTerm;}{\*\cs100 \additive +\f2\cf11\lang1024\langfe1024\noproof tw4winPopup;}{\*\cs101 \additive \f2\cf10\lang1024\langfe1024\noproof tw4winJump;}{\*\cs102 \additive \f2\cf6\lang1024\langfe1024\noproof tw4winInternal;}{\*\cs103 \additive \f2\cf13\lang1024\langfe1024\noproof +DO_NOT_TRANSLATE;}{\s104\ql \li90\ri0\sb120\sa120\sl480\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext104 \slink105 Body Text 2;}{\*\cs105 \additive \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f43\fs19 \sbasedon10 \slink104 \slocked Body Text 2 Char;}{ +\s106\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls10\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon49 \snext106 \slink108 Style Heading 3 Bold + (Asian) Times New Roman 9.5 pt;}{\*\cs107 \additive \rtlch\fcs1 \ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 +\sbasedon10 \slink49 \slocked Heading 3 Bold Char;}{\*\cs108 \additive \rtlch\fcs1 \ab0\af43\afs19 \ltrch\fcs0 \b0\f43\fs19 \sbasedon107 \slink106 \slocked Style Heading 3 Bold + (Asian) Times New Roman 9.5 pt Char;}{\*\cs109 \additive \rtlch\fcs1 +\ab\af43\afs19 \ltrch\fcs0 \b\f43\fs19 \sbasedon10 \slink79 \slocked Body 0 Bold Char;}{\s110\ql \li90\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \ab\af43\afs20\alang1025 \ltrch\fcs0 +\b\fs20\lang1026\langfe2052\super\loch\f43\hich\af43\dbch\af11\cgrid\langnp1026\langfenp2052 \sbasedon0 \snext110 \slink111 LIMPA_T4WINEXTERNAL;}{\*\cs111 \additive \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 +\b\f43\fs20\lang1026\langfe2052\super\langnp1026\langfenp2052 \sbasedon10 \slink110 \slocked LIMPA_T4WINEXTERNAL Char;}{\s112\ql \li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\contextualspace \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\f43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext112 \sqformat \spriority34 \styrsid15671332 List Paragraph;}{\*\cs113 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 +\sbasedon10 \ssemihidden \sunhideused \styrsid9046424 FollowedHyperlink;}{\*\cs114 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf15 \sbasedon10 \ssemihidden \styrsid12455081 Placeholder Text;}{ +\s115\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs21\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f37\hich\af37\dbch\af11\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext115 \slink116 \ssemihidden \sunhideused \styrsid3099185 Plain Text;}{\*\cs116 \additive \rtlch\fcs1 \af0\afs21 \ltrch\fcs0 \f37\fs21 \sbasedon10 \slink115 \slocked \ssemihidden \styrsid3099185 Plain Text Char;}{\*\cs117 \additive +\rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid1472633 Mention;}{\*\cs118 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf22\chshdng0\chcfpat0\chcbpat23 +\sbasedon10 \ssemihidden \sunhideused \styrsid8605371 Unresolved Mention;}{\s119\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 +\fs24\lang1033\langfe1033\loch\f0\hich\af0\dbch\af11\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext119 \ssemihidden \sunhideused \styrsid9006310 Normal (Web);}}{\*\listtable{\list\listtemplateid-1324867376\listhybrid{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listname ;}\listid21824253}{\list\listtemplateid-1286171580\listhybrid{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698709\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li3600\lin3600 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li5760\lin5760 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li6480\lin6480 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li7200\lin7200 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li7920\lin7920 }{\listname ;}\listid103504733}{\list\listtemplateid-2038397894\listhybrid{\listlevel\levelnfc3\levelnfcn3 +\leveljc0\leveljcn0\levelfollow0\levelstartat10\levelspace0\levelindent0{\leveltext\leveltemplateid988212356\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b0\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc4\levelnfcn4 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2\levelnfcn2 +\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4\levelnfcn4 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2\levelnfcn2 +\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4\levelnfcn4 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2\levelnfcn2 +\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\lin6480 }{\listname ;}\listid103960492} +{\list\listtemplateid176468498\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid692200086\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s41 +\fi-358\li3221\jclisttab\tx3223\lin3221 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440 +\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160 +\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880 +\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 +}{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel +\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid196815738} +{\list\listtemplateid-1671774606\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 +\fi-360\li2157\lin2157 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li2877\lin2877 } +{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li3597\lin3597 }{\listlevel +\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li4317\lin4317 }{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5037\lin5037 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li5757\lin5757 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li6477\lin6477 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li7197\lin7197 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li7917\lin7917 }{\listname ;}\listid297614354}{\list\listtemplateid-398971608\listhybrid{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\lin6480 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li7200\lin7200 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li7920\lin7920 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li8640\lin8640 }{\listname ;}\listid362361558}{\list\listtemplateid-1793664660{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0 \ltrch\fcs0 \b\i0\fbias0\hres0\chhres0 \s47\fi-360\li360\jclisttab\tx360\lin360 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0 \ltrch\fcs0 \b\i0\fbias0\hres0\chhres0 \s48\fi-360\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02\'02);}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li1080\jclisttab\tx1080\lin1080 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'03(\'03);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'03(\'04);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li1800\jclisttab\tx1800\lin1800 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'03(\'05);}{\levelnumbers\'02;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li2520\jclisttab\tx2520\lin2520 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li3240\jclisttab\tx3240\lin3240 }{\listname ;}\listid394402059}{\list\listtemplateid1928476992{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af44\afs20 \ltrch\fcs0 \b\i0\f44\fs20\fbias0\hres0\chhres0 \fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s49\fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc3 +\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-358\li1435 +\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 +\b0\i0\strike0\f44\fs20\ulnone\fbias0\hres0\chhres0 \fi-357\li1792\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 +\ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'06.;}{\levelnumbers\'01;} +\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid398796681}{\list\listtemplateid727596770\listhybrid{\listlevel\levelnfc2 +\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc4\levelnfcn4 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1800\lin1800 }{\listlevel\levelnfc2\levelnfcn2\leveljc2 +\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2520\lin2520 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3960\lin3960 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4680\lin4680 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5400\lin5400 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6840\lin6840 }{\listname ;}\listid453669808}{\list\listtemplateid789093748\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid-317712510\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s34\fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689 +\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691 +\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid477573462}{\list\listtemplateid727596770\listhybrid{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1800\lin1800 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2520\lin2520 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3960\lin3960 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4680\lin4680 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5400\lin5400 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6840\lin6840 }{\listname ;}\listid621306136}{\list\listtemplateid-130630884{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s1\fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s2\fi-363\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af43\afs20 \ltrch\fcs0 \b\i0\f43\fs20\fbias0\hres0\chhres0 \s3\fi-357\li1077\jclisttab\tx1440\lin1077 }{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\b0\i0\strike0\f3\fs20\ulnone\fbias0\hres0\chhres0 \s4\fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\b0\i0\strike0\f3\fs20\ulnone\fbias0\hres0\chhres0 \s5\fi-357\li1792\jclisttab\tx2155\lin1792 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\b0\i0\f3\fs20\fbias0\hres0\chhres0 \s6\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\b0\i0\f3\fs20\fbias0\hres0\chhres0 \s7\fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'02i.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s8\fi-357\li2863\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc255\levelnfcn255\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'02A.;}{\levelnumbers;}\rtlch\fcs1 \ab0\ai0\af44\afs20 \ltrch\fcs0 \b0\i0\f44\fs20\fbias0\hres0\chhres0 \s9\fi-358\li3221\jclisttab\tx3223\lin3221 }{\listname ;}\listid752163927}{\list\listtemplateid1638550502 +\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\b0\f3\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc4 +\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2 +\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0 +\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4 +\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2 +\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0 +\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4 +\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2 +\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\lin6480 }{\listname +;}\listid807170055}{\list\listtemplateid-296591990\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s40\fi-357\li2863 +\jclisttab\tx2866\lin2863 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;} +\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760 +\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname +;}\listid810947713}{\list\listtemplateid2100600426\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat6\levelspace0\levelindent0{\leveltext\leveltemplateid1102760264\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\lin6480 }{\listname ;}\listid833034392}{\list\listtemplateid-737381984\listhybrid{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat6\levelspace0\levelindent0{\leveltext +\leveltemplateid-1591064122\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b0\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\lin6480 }{\listname ;}\listid1127315913}{\list\listtemplateid-1813845996\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s39\fi-357\li2506\jclisttab\tx2509\lin2506 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;} +\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600 +\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel +\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1219436735}{\list\listtemplateid1397413230\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat5\levelspace0\levelindent0{\leveltext\leveltemplateid1649943972\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b\fbias0\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\lin6480 }{\listname ;}\listid1549565665} +{\list\listtemplateid303218272\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid612407812\'01\u-3913 ?;}{\levelnumbers;}\f3\cf25\fbias0\hres0\chhres0 \s36 +\fi-358\li1435\jclisttab\tx1437\lin1435 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 } +{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;} +\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760 +\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname +;}\listid1559511898}{\list\listtemplateid-789807850\listhybrid{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid872742900\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \b\fbias0\hres0\chhres0 \s35\fi-357\li1077\jclisttab\tx1080\lin1077 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;} +\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;} +\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} +\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 +\fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320 +\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040 +\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 +}{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname +;}\listid1567649130}{\list\listtemplateid-517838926\listhybrid{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li2880\lin2880 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li4320\lin4320 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li5040\lin5040 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li6480\lin6480 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li7200\lin7200 }{\listname ;}\listid1603076124}{\list\listtemplateid-353719276\listhybrid{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat8\levelspace0\levelindent0{\leveltext +\leveltemplateid82885166\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \b0\fbias0\hres0\chhres0 \fi-360\li2160\lin2160 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\lin1440 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\lin2880 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\lin3600 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\lin5040 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\lin5760 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\lin6480 }{\listname ;}\listid1642882363}{\list\listtemplateid1826634956\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid1391094484\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \fbias0\hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2157\lin2157 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4317\lin4317 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5037\lin5037 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5757\lin5757 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2 +\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6477\lin6477 }{\listname ;}\listid1767533175} +{\list\listtemplateid-627926538\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat6\levelspace0\levelindent0{\leveltext\leveltemplateid-624682846\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 +\b\f43\fs20\fbias0\hres0\chhres0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 +\af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1803\lin1803 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li2523\lin2523 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li3243\lin3243 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li3963\lin3963 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li4683\lin4683 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li5403\lin5403 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-360\li6123\lin6123 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 +\ltrch\fcs0 \hres0\chhres0 \fi-180\li6843\lin6843 }{\listname ;}\listid1767655658}{\list\listtemplateid-154908222\listhybrid{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid-596080174\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \ab\ai0\af0 \ltrch\fcs0 \b\i0\fbias0\hres0\chhres0 \s93\fi-360\li360\jclisttab\tx360\lin360 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1795057320}{\list\listtemplateid-961874242\listhybrid{\listlevel +\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid-1175557160\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s37\fi-357\li1792\jclisttab\tx1795\lin1792 }{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1848404271}{\list\listtemplateid-1299277166\listhybrid{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'00.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li717\lin717 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'01.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li1437\lin1437 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'02.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li2157\lin2157 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'03.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li2877\lin2877 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'04.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li3597\lin3597 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'05.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li4317\lin4317 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698703\'02\'06.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5037\lin5037 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698713\'02\'07.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-360\li5757\lin5757 }{\listlevel\levelnfc2\levelnfcn2\leveljc2\leveljcn2\levelfollow0\levelstartat1\lvltentative +\levelspace0\levelindent0{\leveltext\leveltemplateid67698715\'02\'08.;}{\levelnumbers\'01;}\rtlch\fcs1 \af0 \ltrch\fcs0 \hres0\chhres0 \fi-180\li6477\lin6477 }{\listname ;}\listid1850177709}{\list\listtemplateid-1802592190\listhybrid{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid1229593488\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s38\fi-357\li2149\jclisttab\tx2152\lin2149 }{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1877695764}{\list\listtemplateid-1713720630\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li1080\lin1080 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1800\lin1800 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2520\lin2520 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;} +\f3\fbias0\hres0\chhres0 \fi-360\li3240\lin3240 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 +\fi-360\li3960\lin3960 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4680\lin4680 } +{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5400\lin5400 }{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li6120\lin6120 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6840\lin6840 }{\listname ;}\listid2026209227}{\list\listtemplateid1186249844\listhybrid{\listlevel\levelnfc23 +\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid1637229796\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \s33\fi-357\li357\jclisttab\tx360\lin357 }{\listlevel\levelnfc23\levelnfcn23 +\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\leveltemplateid67698693\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698689\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\leveltemplateid67698691\'01o;}{\levelnumbers;}\f2\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\leveltemplateid67698693 +\'01\u-3929 ?;}{\levelnumbers;}\f10\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid2054619191}}{\*\listoverridetable{\listoverride\listid2054619191\listoverridecount0\ls1}{\listoverride\listid477573462 +\listoverridecount0\ls2}{\listoverride\listid1567649130\listoverridecount0\ls3}{\listoverride\listid1559511898\listoverridecount0\ls4}{\listoverride\listid1848404271\listoverridecount0\ls5}{\listoverride\listid1877695764\listoverridecount0\ls6} +{\listoverride\listid1219436735\listoverridecount0\ls7}{\listoverride\listid810947713\listoverridecount0\ls8}{\listoverride\listid196815738\listoverridecount0\ls9}{\listoverride\listid398796681\listoverridecount0\ls10}{\listoverride\listid394402059 +\listoverridecount0\ls11}{\listoverride\listid752163927\listoverridecount0\ls12}{\listoverride\listid1795057320\listoverridecount0\ls13}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat +\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls14}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1} +{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat +\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls15}{\listoverride\listid2026209227\listoverridecount0\ls16}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat +\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls17}{\listoverride\listid297614354\listoverridecount0\ls18}{\listoverride\listid1850177709\listoverridecount0\ls19}{\listoverride\listid21824253\listoverridecount0\ls20} +{\listoverride\listid362361558\listoverridecount0\ls21}{\listoverride\listid103504733\listoverridecount0\ls22}{\listoverride\listid1642882363\listoverridecount0\ls23}{\listoverride\listid103960492\listoverridecount0\ls24}{\listoverride\listid752163927 +\listoverridecount0\ls25}{\listoverride\listid752163927\listoverridecount0\ls26}{\listoverride\listid1767533175\listoverridecount0\ls27}{\listoverride\listid833034392\listoverridecount0\ls28}{\listoverride\listid1549565665\listoverridecount0\ls29} +{\listoverride\listid1127315913\listoverridecount0\ls30}{\listoverride\listid807170055\listoverridecount0\ls31}{\listoverride\listid1767655658\listoverridecount0\ls32}{\listoverride\listid453669808\listoverridecount9{\lfolevel\listoverridestartat +\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls33}{\listoverride\listid621306136\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat +\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls34}{\listoverride\listid1603076124\listoverridecount0\ls35}{\listoverride\listid752163927\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}\ls36}{\listoverride\listid752163927 +\listoverridecount9{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel\listoverridestartat\levelstartat1}{\lfolevel +\listoverridestartat\levelstartat1}\ls37}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0 +\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0 +\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0 +\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid31619\rsid138238\rsid142056\rsid278067\rsid341127\rsid421856\rsid468211\rsid532396\rsid537980\rsid543100\rsid591170\rsid733240 +\rsid742407\rsid804592\rsid863221\rsid878387\rsid927236\rsid930849\rsid942188\rsid992761\rsid999554\rsid1073171\rsid1121230\rsid1126782\rsid1196305\rsid1277370\rsid1326355\rsid1329577\rsid1451044\rsid1472633\rsid1519413\rsid1522673\rsid1524815\rsid1527996 +\rsid1528546\rsid1574971\rsid1582711\rsid1595803\rsid1639423\rsid1729066\rsid1735716\rsid1770286\rsid1846009\rsid1856079\rsid1909904\rsid1917826\rsid1968038\rsid1982071\rsid1983041\rsid2099865\rsid2102976\rsid2112928\rsid2192282\rsid2194844\rsid2316161 +\rsid2514694\rsid2521189\rsid2560822\rsid2585396\rsid2651997\rsid2706528\rsid2713409\rsid2716316\rsid2718377\rsid2829694\rsid2841082\rsid2914924\rsid2963962\rsid3018435\rsid3020156\rsid3082896\rsid3090423\rsid3099185\rsid3171575\rsid3226043\rsid3289144 +\rsid3289579\rsid3291761\rsid3293678\rsid3297071\rsid3346855\rsid3414719\rsid3503490\rsid3540656\rsid3559666\rsid3671582\rsid3744016\rsid3871134\rsid3894335\rsid3935671\rsid3939038\rsid4005392\rsid4022173\rsid4068868\rsid4093123\rsid4217617\rsid4288165 +\rsid4288919\rsid4347662\rsid4351935\rsid4353404\rsid4552639\rsid4860412\rsid4871695\rsid4944969\rsid5053459\rsid5062890\rsid5072655\rsid5111940\rsid5124098\rsid5133814\rsid5206652\rsid5210069\rsid5328105\rsid5404428\rsid5449220\rsid5464247\rsid5528190 +\rsid5534708\rsid5537145\rsid5578407\rsid5583611\rsid5593316\rsid5660982\rsid5722447\rsid5731110\rsid5774125\rsid5786846\rsid5848687\rsid5857956\rsid5861783\rsid5929695\rsid6061257\rsid6115879\rsid6168189\rsid6170881\rsid6174468\rsid6175996\rsid6178840 +\rsid6316247\rsid6509920\rsid6558882\rsid6576989\rsid6629559\rsid6651166\rsid6686745\rsid6696469\rsid6750755\rsid6831427\rsid6906187\rsid7145177\rsid7148321\rsid7150305\rsid7157427\rsid7171325\rsid7434677\rsid7539718\rsid7541157\rsid7604497\rsid7609200 +\rsid7632984\rsid7677265\rsid7685880\rsid7743015\rsid7752833\rsid7753709\rsid7799211\rsid7818577\rsid7821668\rsid7826697\rsid7959136\rsid7961222\rsid8003087\rsid8027947\rsid8135332\rsid8197462\rsid8210523\rsid8220956\rsid8264692\rsid8331401\rsid8333247 +\rsid8353149\rsid8406101\rsid8525370\rsid8526120\rsid8605371\rsid8662344\rsid8669634\rsid8680518\rsid8849181\rsid8862676\rsid8867950\rsid8879413\rsid8924419\rsid8983484\rsid9006310\rsid9046424\rsid9053936\rsid9183227\rsid9203174\rsid9308700\rsid9308785 +\rsid9329616\rsid9382181\rsid9398536\rsid9596629\rsid9653457\rsid9704396\rsid9767741\rsid9780096\rsid9963461\rsid9976771\rsid10096937\rsid10099906\rsid10168238\rsid10224565\rsid10229021\rsid10309874\rsid10356525\rsid10380985\rsid10686165\rsid10705044 +\rsid10814144\rsid10820326\rsid10881590\rsid10906834\rsid10955554\rsid11162074\rsid11226145\rsid11276212\rsid11278488\rsid11491061\rsid11564628\rsid11616408\rsid11629599\rsid11696035\rsid11803216\rsid11867748\rsid11885578\rsid11885950\rsid11993548 +\rsid12012443\rsid12021150\rsid12068916\rsid12140514\rsid12149217\rsid12218945\rsid12268008\rsid12273991\rsid12386531\rsid12413218\rsid12455081\rsid12526897\rsid12586717\rsid12735952\rsid12744258\rsid12793417\rsid12798252\rsid12812077\rsid12850423 +\rsid13068826\rsid13205063\rsid13240361\rsid13309049\rsid13310631\rsid13394076\rsid13511187\rsid13526881\rsid13566544\rsid13640804\rsid13712247\rsid13829720\rsid13850869\rsid13908149\rsid14091872\rsid14114137\rsid14178614\rsid14185747\rsid14241659 +\rsid14293112\rsid14360409\rsid14368064\rsid14370462\rsid14437949\rsid14441828\rsid14515577\rsid14579117\rsid14638697\rsid14762553\rsid14821375\rsid14842076\rsid14889818\rsid14942691\rsid15024949\rsid15025335\rsid15105390\rsid15232607\rsid15270004 +\rsid15275922\rsid15339860\rsid15490947\rsid15561557\rsid15613399\rsid15663124\rsid15671332\rsid15675979\rsid15690049\rsid15693612\rsid15732197\rsid15734173\rsid15734585\rsid15750326\rsid15867548\rsid15870935\rsid15879797\rsid15941560\rsid16000031 +\rsid16004791\rsid16076221\rsid16208507\rsid16214760\rsid16337640\rsid16386503\rsid16545116\rsid16596813\rsid16610155\rsid16661370\rsid16737391\rsid437025759\rsid1840142422}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0 +\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\creatim\yr2018\mo8\dy28\hr9\min7}{\revtim\yr2018\mo8\dy28\hr9\min7}{\version1}{\edmins0}{\nofpages7}{\nofwords4203}{\nofchars23962}{\nofcharsws28109}{\vern89}}{\*\userprops {\propname MSIP_Labe +l_f42aa342-8706-4288-bd11-ebb85995028c_Enabled}\proptype30{\staticval True}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SiteId}\proptype30{\staticval 72f988bf-86f1-41af-91ab-2d7cd011db47}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995 +028c_Owner}\proptype30{\staticval kevinla@microsoft.com}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_SetDate}\proptype30{\staticval 2018-08-28T16:07:36.8579462Z}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Name}\proptype30 +{\staticval General}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Application}\proptype30{\staticval Microsoft Azure Information Protection}{\propname MSIP_Label_f42aa342-8706-4288-bd11-ebb85995028c_Extended_MSFT_Method}\proptype30 +{\staticval Automatic}{\propname Sensitivity}\proptype30{\staticval General}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}\paperw12240\paperh15840\margl720\margr720\margt720\margb720\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\revisions\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1 +\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\hyphcaps0\formshade\horzdoc\dgmargin\dghspace95\dgvspace180\dghorigin720\dgvorigin720\dghshow2\dgvshow1 +\jexpand\viewkind1\viewscale140\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\rempersonalinfo\allowfieldendsel +\wrppunct\asianbrkrule\rsidroot2829694\newtblstyruls\nogrowautofit\remdttm\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal +\nouicompat \fet0{\*\wgrffmtfilter 013f}\nofeaturethrottle1\ilfomacatclnup15{\*\ftnsep \ltrpar \pard\plain \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid14370462 \rtlch\fcs1 +\af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8879413 \chftnsep +\par }}{\*\ftnsepc \ltrpar \pard\plain \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid14370462 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8879413 \chftnsepc +\par }}{\*\aftnsep \ltrpar \pard\plain \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid14370462 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8879413 \chftnsep +\par }}{\*\aftnsepc \ltrpar \pard\plain \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid14370462 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8879413 \chftnsepc +\par }}\ltrpar \sectd \ltrsect\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3 +\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar +\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid14370462 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af43 \ltrch\fcs0 \b\lang1036\langfe1033\langnp1036\insrsid9780096\charrsid7753709 +\par }\pard \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid5210069 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid9780096\charrsid7753709 \hich\af43\dbch\af11\loch\f43 +MICROSOFT SOFTWARE LICENSE TERMS +\par \hich\af43\dbch\af11\loch\f43 MICROSOFT WINDOWS SOFTWARE DEVELOPMENT KIT}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid1582711\charrsid7753709 \hich\af43\dbch\af11\loch\f43 (SDK) FOR WINDOWS 10}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid9780096\charrsid7753709 +\hich\af43\dbch\af11\loch\f43 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid5210069\charrsid543100 \hich\af43\dbch\af11\loch\f43 _______________________________________________________________________________________________________ +\par }\pard \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid5722447 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 +These license terms are an agreement between Microsoft Corporation (or based on where you live, one of its affiliates) and you. Please read them. They a\hich\af43\dbch\af11\loch\f43 +pply to the software named above, which includes the media on which you received it, if any. The terms also apply to any Microsoft +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid927236 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s34\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid5722447 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid927236 \hich\af43\dbch\af11\loch\f43 APIs (i.e., APIs included with the installation of the SDK or APIs accessed by installing extension packages or service to \hich\af43\dbch\af11\loch\f43 use with the SDK),}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid5133814 +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 updates, +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 supplements, +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid7171325 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard \ltrpar\s34\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid6629559 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid7171325 \hich\af43\dbch\af11\loch\f43 i}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid7171325\charrsid543100 +\hich\af43\dbch\af11\loch\f43 nternet}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 -based services, and +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard \ltrpar\s34\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 support services +\par }\pard\plain \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 for this software, unless other terms accompany those items. If so, those terms apply. +\par \hich\af43\dbch\af11\loch\f43 By using the software, you accept these terms. If you do not accept them, \hich\af43\dbch\af11\loch\f43 do not use the software.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid5053459\charrsid543100 \hich\af43\dbch\af11\loch\f43 + +\par \hich\af43\dbch\af11\loch\f43 As described below, using some features also operates as your consent to the transmission of certain standard computer information for Internet-based services.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid930849\charrsid543100 + +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid5210069\charrsid543100 \hich\af43\dbch\af11\loch\f43 ________________________________________________________________________________________________}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid930849\delrsid930849\charrsid543100 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid5210069\charrsid543100 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 If you comply with these license terms, you have the rights below. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 1.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid1639423 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 INSTALLATION}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 AND }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 +\hich\af43\dbch\af11\loch\f43 USE RIGHTS. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid16208507 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid1126782 \hich\af43\dbch\af11\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\qj \fi-360\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid1472633 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid1126782 \hich\af43\dbch\af11\loch\f43 +You may install and use any number of copies of the software on your devices to design, develop and test your programs that run on a Microsoft operating system. Further, you may install, use and/or deploy via a network management system or as part of a de +\hich\af43\dbch\af11\loch\f43 s\hich\af43\dbch\af11\loch\f43 +ktop image, any number of copies of the software on computer devices within your internal corporate network to design, develop and test your programs that run on a Microsoft operating system. Each copy must be comp}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid2706528 \hich\af43\dbch\af11\loch\f43 l}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid1126782 \hich\af43\dbch\af11\loch\f43 ete, including all copyright and trademar\hich\af43\dbch\af11\loch\f43 +k notices. You must require end users to agree to terms that protect the software as much as these license terms. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14638697 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 b.\tab}}\pard \ltrpar\s2\qj \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid1472633 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 Utilities. }{\rtlch\fcs1 \af43 +\ltrch\fcs0 \b0\insrsid10820326\charrsid1126782 \hich\af43\dbch\af11\loch\f43 The software contains certain components that are identified in the Utilities List located at}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid3297071\charrsid1126782 +\hich\af43\dbch\af11\loch\f43 }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4860412 \hich\af43\dbch\af11\loch\f43 HYPERLINK "http://go.microsoft.\hich\af43\dbch\af11\loch\f43 com/fwlink/?LinkId=524839" }{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid2706528 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b7400000068007400740070003a002f002f0067006f002e006d006900630072006f0073006f00660074002e0063006f006d002f00660077006c0069006e006b002f003f004c0069006e006b00490064003d0035003200 +34003800330039000000795881f43b1d7f48af2c825dc485276300000000a5ab000100690d00002eff0030690020006800000000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\b0\ul\cf2\insrsid4068868\charrsid9704396 \hich\af43\dbch\af11\loch\f43 +http://go.microsoft.com/fwlink/?LinkId=524839}}}\sectd \ltrsect\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid10820326\charrsid1126782 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid4068868 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid10820326\charrsid1126782 \hich\af43\dbch\af11\loch\f43 Depending on the specific edition of the software, t\hich\af43\dbch\af11\loch\f43 +he number of Utility files you receive with the software may not be equal to the number of Utilities listed in the Utilities List. Except as otherwise provided on the Utilities List for specific files, you may copy and install the Utilities you receive +\hich\af43\dbch\af11\loch\f43 w\hich\af43\dbch\af11\loch\f43 +ith the software on to other third party machines. These Utilities may only be used to debug and deploy your programs and databases you have developed with the software. You must delete all the Utilities installed onto a third}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \b0\insrsid7157427 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid10820326\charrsid1126782 \hich\af43\dbch\af11\loch\f43 party machine within the ear\hich\af43\dbch\af11\loch\f43 +lier of (i) when you have finished debugging or deploying your programs; or (ii) thirty (30) days after installation of the Utilities onto that machine. We may add additional files to this list from time to time.}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid15232607\charrsid1126782 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 c.\tab}}\pard \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid16545116 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 Build }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid5929695 \hich\af43\dbch\af11\loch\f43 Service}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid7150305 \hich\af43\dbch\af11\loch\f43 s and Enterprise Build Servers}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid5929695 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid10820326\charrsid543100 \~\hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid5929695 \hich\af43\dbch\af11\loch\f43 You}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11696035 \hich\af43\dbch\af11\loch\f43 may }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \b0\insrsid3291761 \hich\af43\dbch\af11\loch\f43 install and use any number of copies of the software }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 onto your build machines}{\rtlch\fcs1 +\af43 \ltrch\fcs0 \b0\insrsid11696035 \hich\af43\dbch\af11\loch\f43 or servers}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 , solely for the purpose of}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11696035 +\hich\af43\dbch\af11\loch\f43 :}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11696035\charrsid7150305 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid11696035 \hich\af43\dbch\af11\loch\f43 i.\tab}}\pard\plain \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid7150305 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11696035 \hich\af43\dbch\af11\loch\f43 C}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 +\hich\af43\dbch\af11\loch\f43 ompiling, building, verifying and archiving your programs}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11696035 \hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \ab\af43 \ltrch\fcs0 \b\insrsid11696035 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid11696035 \hich\af43\dbch\af11\loch\f43 ii.\tab}}\pard \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid14368064 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11696035 \hich\af43\dbch\af11\loch\f43 Creating and configuring build +\hich\af43\dbch\af11\loch\f43 systems internal to your organization to support your internal build environment;}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid15732197 \hich\af43\dbch\af11\loch\f43 or}{\rtlch\fcs1 \ab\af43 \ltrch\fcs0 \b\insrsid11696035 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid11696035 \hich\af43\dbch\af11\loch\f43 iii.\tab}}\pard \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid7150305 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11696035 \hich\af43\dbch\af11\loch\f43 +Enabling a service for third parties to design, develop and test programs or services that run on a Microsoft operating system. }{\rtlch\fcs1 \ab\af43 \ltrch\fcs0 \b\insrsid10820326\charrsid11696035 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 d.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid5722447 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 Included Microsoft Programs.}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 The s\hich\af43\dbch\af11\loch\f43 oftware contains other Microsoft programs. The license terms with those programs apply to your use of them. +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid2514694\charrsid543100 \hich\af43\dbch\af11\loch\f43 e.\tab}}\pard \ltrpar\s2\qj \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2514694\charrsid543100 \hich\af43\dbch\af11\loch\f43 Third Party Notices.}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \b0\insrsid2514694\charrsid543100 \hich\af43\dbch\af11\loch\f43 The software may include third party code that Microsoft, not the third party, licenses to you under this agreement. Notice\hich\af43\dbch\af11\loch\f43 s, if any, for the third +}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7157427 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid2514694\charrsid543100 \hich\af43\dbch\af11\loch\f43 +party code are included for your information only. Notices, if any, for this third}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7157427 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid2514694\charrsid543100 +\hich\af43\dbch\af11\loch\f43 party code are included with the software and may be located at}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid8003087 \hich\af43\dbch\af11\loch\f43 }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid1472633\charrsid7157427 \hich\af43\dbch\af11\loch\f43 HYPERLINK "http://aka.ms/thirdpartynotices" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9596629\charrsid7157427 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b5800000068007400740070003a002f002f0061006b0061002e006d0073002f0074006800690072006400700061007200740079006e006f00740069006300650073000000795881f43b1d7f48af2c825dc48527630000 +0000a5ab000300032e50000000030000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\b0\ul\cf2\insrsid1472633\charrsid7157427 \hich\af43\dbch\af11\loch\f43 http://aka.ms/thirdpartynotices}}}\sectd \ltrsect +\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7157427 .}{\rtlch\fcs1 \af44 \ltrch\fcs0 \b0\f44\cf24\insrsid1472633\charrsid1472633 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\cf24\insrsid7157427 \hich\af43\dbch\af11\loch\f43 2.\tab}}\pard\plain \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid8406101 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af44 \ltrch\fcs0 \b0\f44\cf24\insrsid7157427 \page }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9329616\charrsid543100 \hich\af43\dbch\af11\loch\f43 A\hich\af43\dbch\af11\loch\f43 DDITIONAL LICENSING REQUIREMENTS AND/OR USE RIGHTS. +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid1126782 \hich\af43\dbch\af11\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid5722447 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 Distributable Code. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid1126782 +\hich\af43\dbch\af11\loch\f43 The software contains code that you are permitted to distribute in programs you develop if you comply with the terms below. +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid1126782 \hich\af43\dbch\af11\loch\f43 i.\tab}}\pard\plain \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid1639423 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid1126782 \hich\af43\dbch\af11\loch\f43 \hich\f43 Right to Use and Distribute. The code and test files listed below are \'93 +\loch\f43 \hich\f43 Distributable Code\'94.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782\charrsid1126782 +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782\charrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s4\qj \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid1126782\charrsid1126782 \hich\af43\dbch\af11\loch\f43 RE}{\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid1126782\charrsid1639423 +\hich\af43\dbch\af11\loch\f43 DIST.TXT Files. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 You may copy and distribute the object code for}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid468211 \hich\af43\dbch\af11\loch\f43 m}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 of code listed in }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4068868 \hich\af43\dbch\af11\loch\f43 REDIST}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 +\hich\af43\dbch\af11\loch\f43 .TXT files plus the files listed on the REDIST.TXT list located at}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4068868 \hich\af43\dbch\af11\loch\f43 }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4860412 +\hich\af43\dbch\af11\loch\f43 HYPERLINK "http://go.microsoft.com/fwlink/?LinkId=524842" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2706528 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b7400000068007400740070003a002f002f0067006f002e006d006900630072006f0073006f00660074002e0063006f006d002f00660077006c0069006e006b002f003f004c0069006e006b00490064003d0035003200 +34003800340032000000795881f43b1d7f48af2c825dc485276300000000a5ab00010075004b1c17ff00f7b56bb6000000000900}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\ul\cf2\insrsid4068868\charrsid9704396 \hich\af43\dbch\af11\loch\f43 +http://go.microsoft.com/fwlink/?LinkId=524842}}}\sectd \ltrsect\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4068868 \hich\af43\dbch\af11\loch\f43 . }{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 Depending on the specific edition of the software, the number of REDIST files you receive with the software may not be equal to the numbe\hich\af43\dbch\af11\loch\f43 +r of REDIST files listed in the REDIST.TXT List. We may add additional files to the list from time to time.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid1126782\charrsid1639423 +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 +Third Party Distribution. You may permit distributors of your programs to copy and distribute the Distributable Code as part of those programs. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid1126782\charrsid1639423 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid1126782 \hich\af43\dbch\af11\loch\f43 ii.\tab}}\pard\plain \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid1639423 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid1126782 \hich\af43\dbch\af11\loch\f43 Distribution Requirements. For any Distributable Code you distribute, you must}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782\charrsid1126782 +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s4\qj \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 Add significant primary functionality to it in your programs; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 +For any Distributable Code having a filename extension of .lib, distribute only the results of running such\hich\af43\dbch\af11\loch\f43 Distributable Code through a linker with your program; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 +Distribute Distributable Code included in a setup program only as part of that setup program without modification; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 Require distributors and external end users to agree to terms that protect it at +\hich\af43\dbch\af11\loch\f43 least as much as this agreement; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 +For Distributable Code from the Windows Performance Toolkit portions of the software, distribute the unmodified software package as a whole with your programs, with the exception of the KernelTraceControl.dll and the Wind\hich\af43\dbch\af11\loch\f43 +owsPerformanceRecorderControl.dll which can be distributed with your programs; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 Display your valid copyright notice on your programs; and +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 Indemnify, defend, and hold harmless Microsoft from any claims, including attorneys\hich\f43 +\rquote \loch\f43 fees, related to the distribut\hich\af43\dbch\af11\loch\f43 ion or use of your programs. +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid1126782 \hich\af43\dbch\af11\loch\f43 iii.\tab}}\pard\plain \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid1639423 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid1126782 \hich\af43\dbch\af11\loch\f43 Distribution Restrictions. You may not}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid1126782\charrsid1126782 +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s4\qj \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 Alter any copyright, trademark or patent notice in the Distributable Code; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 Use Microsoft\hich\f43 \rquote \loch\f43 s trademarks in your programs\hich\f43 \rquote +\loch\f43 names or in a way that suggests your programs come from or\hich\af43\dbch\af11\loch\f43 are endorsed by Microsoft; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard \ltrpar\s4\qj \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid1472633 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 +Distribute partial copies of the Windows Performance Toolkit portion of the software package with the exception of the KernelTraceControl.dll and the W}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1472633 \hich\af43\dbch\af11\loch\f43 i}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 ndowsPerformanceRecorderControl.dll which can be distributed with your pro\hich\af43\dbch\af11\loch\f43 grams; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard \ltrpar\s4\qj \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 +Distribute Distributable Code to run on a platform other than the Microsoft operating system platform; +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 Include Distributable Code in malicious, deceptive or unlawful programs; or +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 +Modified or distribute the source code of any Distributable Code so that any part of it becomes subject to an Excluded License. And Excluded License is on}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14762553 \hich\af43\dbch\af11\loch\f43 e}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 that requir\hich\af43\dbch\af11\loch\f43 es, as a condition of use, modification or distribution, that +\par {\listtext\pard\plain\ltrpar \s6 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s6\ql \fi-357\li2149\ri0\sb120\sa120\widctlpar +\jclisttab\tx2152\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl5\outlinelevel5\adjustright\rin0\lin2149\itap0\pararsid1639423 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 The code be disclosed or \hich\af43\dbch\af11\loch\f43 distributed in source code form; or + +\par {\listtext\pard\plain\ltrpar \s6 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid1126782 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 Other}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10955554 \hich\af43\dbch\af11\loch\f43 s}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782 \hich\af43\dbch\af11\loch\f43 have the right to modify it.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid1126782\charrsid1126782 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid12455081 \hich\af43\dbch\af11\loch\f43 b.\tab}}\pard\plain \ltrpar\s2\qj \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid2560822 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081 \hich\af43\dbch\af11\loch\f43 Additional }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid7541157 \hich\af43\dbch\af11\loch\f43 +Rights and Restrictions}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081 \hich\af43\dbch\af11\loch\f43 for Features made Available with the Software. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid12455081\charrsid8406101 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid12455081\charrsid8406101 \hich\af43\dbch\af11\loch\f43 i.\tab}}\pard\plain \ltrpar\s3\qj \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081\charrsid8406101 \hich\af43\dbch\af11\loch\f43 Windows }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid15867548 +\hich\af43\dbch\af11\loch\f43 A}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid1770286 \hich\af43\dbch\af11\loch\f43 pp}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid1770286\charrsid8406101 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b\insrsid12455081\charrsid8406101 \hich\af43\dbch\af11\loch\f43 Requirements.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081\charrsid543100 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081\charrsid16208507 +\hich\af43\dbch\af11\loch\f43 If you intend to make your program available in the Windows Store, \hich\af43\dbch\af11\loch\f43 +the program must comply with the Certification Requirements as defined and described in the App Developer Agreement, currently available at:}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081\charrsid543100 \hich\af43\dbch\af11\loch\f43 } +{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4860412 \hich\af43\dbch\af11\loch\f43 HYPERLINK "https://msdn.microsoft.com/en-us/library/windows/apps/hh694058.aspx" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2706528 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90ba0000000680074007400700073003a002f002f006d00730064006e002e006d006900630072006f0073006f00660074002e0063006f006d002f0065006e002d00750073002f006c006900620072006100720079002f00 +770069006e0064006f00770073002f0061007000700073002f00680068003600390034003000350038002e0061007300700078000000795881f43b1d7f48af2c825dc485276300000000a5ab00010001000057fe4a3400696f6a007200d0fb00}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 +\cs73\ul\cf2\insrsid12413218\charrsid9704396 \hich\af43\dbch\af11\loch\f43 https://msdn.microsoft.com/en-us/library/windows/apps/hh694058.aspx}}}\sectd \ltrsect\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj { +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12413218 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid12455081\charrsid543100 \hich\af43\dbch\af11\loch\f43 ii.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081\charrsid543100 \hich\af43\dbch\af11\loch\f43 +Bing Maps. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081\charrsid543100 \hich\af43\dbch\af11\loch\f43 \hich\f43 +The software may include features that retrieve content such as maps, images and other data through the Bing Maps (or successor branded) application programming interface (the \'93\loch\f43 \hich\f43 Bing Maps API\'94\loch\f43 +) to create reports displaying data on top of maps, aerial and h\hich\af43\dbch\af11\loch\f43 y\hich\af43\dbch\af11\loch\f43 +brid imagery. If these features are included, you may use these features to create and view dynamic or static documents only in conjunction with and through methods and means of access integrated in the software. You may not otherwise copy, store, archive +,\hich\af43\dbch\af11\loch\f43 + or create a database of the entity information including business names, addresses and geocodes available through the Bing Maps API. You may not use the Bing Maps API to provide sensor based guidance/routing, nor use any Road Traffic Data or Bird +\hich\f43 \rquote \loch\f43 s Eye I\hich\af43\dbch\af11\loch\f43 m\hich\af43\dbch\af11\loch\f43 +ager (or associated metadata) even if available through the Bing Maps API for any purpose. Your use of the Bing Maps API and associated content is also subject to the additional terms and conditions at }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid4860412 \hich\af43\dbch\af11\loch\f43 HYPERLINK "http://go.microsoft.com/fwlink/?LinkId=219\hich\af43\dbch\af11\loch\f43 69" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2706528 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b7200000068007400740070003a002f002f0067006f002e006d006900630072006f0073006f00660074002e0063006f006d002f00660077006c0069006e006b002f003f004c0069006e006b00490064003d0032003100 +3900360039000000795881f43b1d7f48af2c825dc485276300000000a5ab00016500ff416977ff6e2e226f1000b673000000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\ul\cf2\insrsid12455081\charrsid543100 \hich\af43\dbch\af11\loch\f43 +http://go.microsoft.com/fwlink/?LinkId=21969}}}\sectd \ltrsect\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081\charrsid543100 . +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid12455081\charrsid7961222 \hich\af43\dbch\af11\loch\f43 iii.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081\charrsid7961222 \hich\af43\dbch\af11\loch\f43 +Additional Mapping APIs. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12455081\charrsid10881590 \hich\af43\dbch\af11\loch\f43 The software may include application programming \hich\af43\dbch\af11\loch\f43 \hich\f43 +interfaces that provide maps and other related mapping features and services that are not provided by Bing (the \'93\loch\f43 \hich\f43 Additional Mapping APIs\'94\loch\f43 +). These Additional Mapping APIs are subject to additional terms and conditions and may require payment of fees to Mic\hich\af43\dbch\af11\loch\f43 r\hich\af43\dbch\af11\loch\f43 +osoft and/or third party providers based on the use or volume of use of such Additional Mapping APIs. These terms and conditions will be provided when you obtain any necessary license keys to use such Additional Mapping APIs or when you review or receive +\hich\af43\dbch\af11\loch\f43 d\hich\af43\dbch\af11\loch\f43 ocumentation related to the use of such Additional Mapping APIs.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081\charrsid7961222 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid12455081 \hich\af43\dbch\af11\loch\f43 iv.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081 \hich\af43\dbch\af11\loch\f43 Push Notifications. }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \insrsid12455081\charrsid10881590 \hich\af43\dbch\af11\loch\f43 The Microsoft Push Notification Service may not be used to send notifications that are mission critical or otherwise could affect matters of life or death, including w +\hich\af43\dbch\af11\loch\f43 +ithout limitation critical notifications related to a medical device or condition. MICROSOFT EXPRESSLY DISCLAIMS ANY WARRANTIES THAT THE USE OF THE MICROSOFT PUSH NOTIFICATION SERVICE OR DELIVERY OF MICROSOFT PUSH NOTIFICATION SERVICE NOTIFICATIONS WILL B +\hich\af43\dbch\af11\loch\f43 E\hich\af43\dbch\af11\loch\f43 UNINTERRUPTED, ERROR FREE, OR OTHERWISE GUARANTEED TO OCCUR ON A REAL-TIME BASIS.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081\charrsid8210523 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid12455081 \hich\af43\dbch\af11\loch\f43 v.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081 \hich\af43\dbch\af11\loch\f43 Speech namespace API. }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \insrsid2194844\charrsid10881590 \hich\af43\dbch\af11\loch\f43 Using speech recognition functionality via the Speech namespace APIs in a program requires the support of a speech recognition service. The service +\hich\af43\dbch\af11\loch\f43 +may require network connectivity at the time of recognition (e.g., when using a predefined grammar). In addition, the service may also collect speech-related data in order to provide and improve the service. The speech-related data may include, for exampl +\hich\af43\dbch\af11\loch\f43 e\hich\af43\dbch\af11\loch\f43 , information related to grammar size and string phrases in a grammar.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid12455081\charrsid8210523 +\par }\pard \ltrpar\s3\qj \fi-357\li1077\ri0\sb120\sa120\widctlpar\tx1077\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8210523 \tab }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid2194844 \hich\af43\dbch\af11\loch\f43 Also, }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2194844\charrsid992761 \hich\af43\dbch\af11\loch\f43 +in order for a user to use speech recognition on the phone they must first accept certain terms of use. The terms of use notify the user that data related to their use of the spe\hich\af43\dbch\af11\loch\f43 +ech recognition service will be collected and used to provide and improve the service. If a user does not accept the terms of use and speech recognition is attempted by the application, the operation will not work and an error will be returned to the appl +\hich\af43\dbch\af11\loch\f43 i\hich\af43\dbch\af11\loch\f43 cation.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2194844 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8210523 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid8210523 \hich\af43\dbch\af11\loch\f43 vi.\tab}}\pard \ltrpar +\s3\qj \fi-360\li1080\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1080\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid8210523 \hich\af43\dbch\af11\loch\f43 PlayReady Support. }{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8210523\charrsid543100 \hich\af43\dbch\af11\loch\f43 The software may include the Windows Emulator, which contains Microsoft\hich\f43 \rquote \loch\f43 s PlayReady co\hich\af43\dbch\af11\loch\f43 +ntent access technology. Content owners use Microsoft PlayReady content access technology to protect their intellectual property, including copyrighted content. This software uses PlayReady technology to access PlayReady-protected content and/or WMDRM-p +\hich\af43\dbch\af11\loch\f43 r\hich\af43\dbch\af11\loch\f43 otected content. Microsoft may decide to revoke the software\hich\f43 \rquote \loch\f43 +s ability to consume PlayReady-protected content for reasons including but not limited to (i) if a breach or potential breach of PlayReady technology occurs, (ii) proactive robustness enhanceme\hich\af43\dbch\af11\loch\f43 n\hich\af43\dbch\af11\loch\f43 +t, and (iii) if Content owners require the revocation because the software fails to properly enforce restrictions on content usage. Revocation should not affect unprotected content or content protected by other content access technologies. Content owner +\hich\af43\dbch\af11\loch\f43 s\hich\af43\dbch\af11\loch\f43 + may require you to upgrade PlayReady to access their content. If you decline an upgrade, you will not be able to access content that requires the upgrade and may not be able to install other operating system updates or upgrades.}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \b\insrsid8210523 \hich\af43\dbch\af11\loch\f43 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid8027947 \hich\af43\dbch\af11\loch\f43 vii.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid8027947 \hich\af43\dbch\af11\loch\f43 Package Managers. }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \insrsid8027947 \hich\af43\dbch\af11\loch\f43 The software may include package managers, like NuGet, }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid8027947\charrsid10881590 \hich\af43\dbch\af11\loch\f43 +that give you the option to download other Microsoft and third party software packages to use with your application. Those packages are under their own licenses, and not this agreement. Microsoft does \hich\af43\dbch\af11\loch\f43 +not distribute, license or provide any warranties for any of the third party packages.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid8027947\charrsid6831427 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid8027947 \hich\af43\dbch\af11\loch\f43 viii.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid8027947 \hich\af43\dbch\af11\loch\f43 Font Components. }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \insrsid8027947 \hich\af43\dbch\af11\loch\f43 +While the software is running, you may use its fonts to display and print content. You may only embed fonts in content as permitted by the embedding restrictions in the fonts; and temporarily download them to a prin}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid6831427 \hich\af43\dbch\af11\loch\f43 t}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6178840 \hich\af43\dbch\af11\loch\f43 e}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6831427 \hich\af43\dbch\af11\loch\f43 r or other output device to help print +\hich\af43\dbch\af11\loch\f43 content. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid8027947\charrsid6831427 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid6831427 \hich\af43\dbch\af11\loch\f43 ix.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid6831427 \hich\af43\dbch\af11\loch\f43 +Notice about the H.264/AVD Visual Standard, and the VC-1 Video Standard. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6831427 \hich\af43\dbch\af11\loch\f43 +This software may include H.264/MPEG-4 AVC and/or VD-1 decoding technology. MPEG LA, L.L.C. requires this notice: }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid6831427\charrsid6831427 +\par }\pard\plain \ltrpar\s2\qj \li1440\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin1440\itap0\pararsid2560822 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid6831427\charrsid543100 \hich\af43\dbch\af11\loch\f43 THIS PRODUCT IS LICENSED UNDER THE AVC AND THE VC-1 PA +\hich\af43\dbch\af11\loch\f43 \hich\f43 TENT PORTFOLIO LICENSES FOR THE PERSONAL AND NON-COMMERCIAL USE OF A CONSUMER TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE ABOVE STANDARDS (\'93\loch\f43 \hich\f43 VIDEO STANDARDS\'94\loch\f43 +) AND/OR (ii) DECODE AVC, AND VC-1 VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL AND NO\hich\af43\dbch\af11\loch\f43 N\hich\af43\dbch\af11\loch\f43 +-COMMERCIAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE SUCH VIDEO. NONE OF THE LICENSES EXTEND TO ANY OTHER PRODUCT REGARDLESS OF WHETHER SUCH PRODUCT IS INCLUDED WITH THIS SOFTWARE IN A SINGLE ARTICLE. NO LICENSE IS GRANTED OR +\hich\af43\dbch\af11\loch\f43 \hich\af43\dbch\af11\loch\f43 SHALL BE IMPLIED FOR ANY OTHER USE. ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4860412 +\hich\af43\dbch\af11\loch\f43 HYPERLINK "http://WWW.MPEGLA.COM" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2706528 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b4600000068007400740070003a002f002f007700770077002e006d007000650067006c0061002e0063006f006d002f000000795881f43b1d7f48af2c825dc485276300000000a5ab000114000000816eff2200220003 +000036000003}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\b0\ul\cf2\insrsid6831427\charrsid7818577 \hich\af43\dbch\af11\loch\f43 WWW.MPEGLA.COM}}}\sectd \ltrsect +\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid6831427\charrsid543100 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid6831427 +\par }\pard \ltrpar\s2\qj \li1077\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin1077\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid6831427\charrsid543100 \hich\af43\dbch\af11\loch\f43 +For clarification purposes, this notice does not limit or inhibit the use of the software for normal business uses that are personal to that business which do not include (i) redistribution of the software to third parties, or (ii) creation of content wit +\hich\af43\dbch\af11\loch\f43 h the VIDEO STANDARDS compliant technologies for distribution to third parties. +\par {\*\bkmkstart _MailEndCompose}{\*\bkmkend _MailEndCompose}{\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 3.\tab}}\pard\plain \ltrpar +\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid8406101 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 +INTERNET-BASED SERVICES. Microsoft provides Internet-based services with the software. It may change or cancel them at any time.}{\rtlch\fcs1 \af43\afs20 \ltrch\fcs0 \fs20\dbch\af31505\insrsid4022173\charrsid543100 \hich\af43\dbch\af31505\loch\f43 }{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\qj \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid2560822 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 Consent fo\hich\af43\dbch\af11\loch\f43 r Internet-Based Services.}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 The software features described below and in the privacy statement at }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4860412 +\hich\af43\dbch\af11\loch\f43 HYPERLINK "http://go.microsoft.com/fwlink/?LinkId=506737" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2706528 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b7400000068007400740070003a002f002f0067006f002e006d006900630072006f0073006f00660074002e0063006f006d002f00660077006c0069006e006b002f003f004c0069006e006b00490064003d0035003000 +36003700330037000000795881f43b1d7f48af2c825dc485276300000000a5ab000100000000ff65ff6900522012000070000000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\b0\ul\cf2\insrsid15867548\charrsid543100 \hich\af43\dbch\af11\loch\f43 +http://go.microsoft.com/fwlink/?LinkId=5}{\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\b0\ul\cf2\insrsid15867548 \hich\af43\dbch\af11\loch\f43 21839}}}\sectd \ltrsect\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj { +\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid15867548\charrsid543100 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 connect to Microsoft or service provider computer systems +\hich\af43\dbch\af11\loch\f43 over the Internet. In some cases, you will not receive a separate notice when they connect. In some cases, you may switch off these features or not use them}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid13566544 +\hich\af43\dbch\af11\loch\f43 as described in the applicable product documentation}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 . By using these features, you consent to the t +\hich\af43\dbch\af11\loch\f43 ransmission of this information. Microsoft does not use the information to identify or contact you. +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 i.\tab}}\pard\plain \ltrpar\s3\qj \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af43 \ltrch\fcs0 \b\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 Computer Information.}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 The following features use Internet protocols, which send to the appropriate systems computer information, such as your Internet pr\hich\af43\dbch\af11\loch\f43 +otocol address, the type of operating system, browser, and name and version of the software you are using, and the language code of the device where you installed the software. Microsoft uses this information to make the Internet-based services available +\hich\af43\dbch\af11\loch\f43 t\hich\af43\dbch\af11\loch\f43 o you. +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid13566544 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s4\qj \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid927236 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid13566544 \hich\af43\dbch\af11\loch\f43 Software Use and Performance}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid13566544\charrsid927236 \hich\af43\dbch\af11\loch\f43 . }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid927236\charrsid927236 \hich\af43\dbch\af11\loch\f43 This software}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14942691\charrsid14942691 +\hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14942691 \hich\af43\dbch\af11\loch\f43 collects info }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14942691\charrsid543100 \hich\af43\dbch\af11\loch\f43 +about your hardware and how you use the software}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14942691 \hich\af43\dbch\af11\loch\f43 and }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid927236\charrsid927236 \hich\af43\dbch\af11\loch\f43 +automatically sends error reports to Microsoft.\~ These reports include information about problems that occur in the software.\~ Reports \hich\af43\dbch\af11\loch\f43 +might unintentionally contain personal information. For example, a report that contains a snapshot of computer memory might include your name. Part of a document you were working on could be included as well, but this information in reports }{\rtlch\fcs1 +\af43 \ltrch\fcs0 \insrsid14942691 \hich\af43\dbch\af11\loch\f43 or any info col\hich\af43\dbch\af11\loch\f43 lected about }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid15879797 \hich\af43\dbch\af11\loch\f43 hardware or your software use }{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid927236\charrsid927236 \hich\af43\dbch\af11\loch\f43 will not be used to identify or contact you. +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid10229021\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard \ltrpar\s4\qj \fi-358\li1435\ri0\sb120\sa120\widctlpar +\jclisttab\tx1437\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl3\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid10229021\charrsid543100 \hich\af43\dbch\af11\loch\f43 Digital Certificates}{\rtlch\fcs1 +\af43 \ltrch\fcs0 \insrsid10229021\charrsid927236 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10229021\charrsid543100 \hich\af43\dbch\af11\loch\f43 + The software uses digital certificates. These digital certificates confirm the identity of Internet users sending X.509 standard encryption info\hich\af43\dbch\af11\loch\f43 +rmation. They also can be used to digitally sign files and macros to verify the integrity and origin of the file contents. The software retrieves certificates and updates certificate revocation lists using the Internet, when available. +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid4022173\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 +Windows Application Certification Kit}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 +. To ensure you have the latest certification tests, when launched this software periodically checks a Windows Application Certification Kit file on download.microsft.com to see if an update is available.\~ If an update \hich\af43\dbch\af11\loch\f43 +is found, you are prompted and provided a link to a web site where you can download the update.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid15867548 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \cf25\insrsid15867548\charrsid5861783 +\hich\af43\dbch\af11\loch\f43 You may use the Windows Application Certification Kit solely to test your programs before you submit them for a potential Microsoft Windows Certification and fo\hich\af43\dbch\af11\loch\f43 +r inclusion on the Microsoft Windows Store. The results you receive are for informational purposes only. Microsoft has no obligation to either (i) provid}{\rtlch\fcs1 \af43 \ltrch\fcs0 \cf25\insrsid5861783\charrsid5861783 \hich\af43\dbch\af11\loch\f43 e}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \cf25\insrsid15867548\charrsid5861783 \hich\af43\dbch\af11\loch\f43 you with a Windows Certification for your programs and/or ii) include your program }{\rtlch\fcs1 \af43 \ltrch\fcs0 \cf25\insrsid5861783\charrsid5861783 +\hich\af43\dbch\af11\loch\f43 i}{\rtlch\fcs1 \af43 \ltrch\fcs0 \cf25\insrsid15867548\charrsid5861783 \hich\af43\dbch\af11\loch\f43 n the Microsoft W\hich\af43\dbch\af11\loch\f43 indows Store. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\cf25\insrsid6174468\charrsid5861783 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs20\insrsid5464247\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid5464247\charrsid543100 \hich\af43\dbch\af11\loch\f43 +Microsoft Digital Rights Management for Silverlight. +\par }\pard \ltrpar\s4\qj \li1435\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid5464247\charrsid543100 \hich\af43\dbch\af11\loch\f43 +If you use Silverlight to access content that has been protected with Microsoft Digital Rights Management}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9963461\charrsid543100 \hich\af43\dbch\af11\loch\f43 + (DRM), in order to let you play the content, the software may automatically +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9963461\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard \ltrpar +\s4\qj \fi-360\li2880\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls21\outlinelevel3\adjustright\rin0\lin2880\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9963461\charrsid543100 \hich\af43\dbch\af11\loch\f43 +request media usage rights from a rights server on the Internet and +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9963461\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 download and install available DRM Updates. +\par }\pard \ltrpar\s4\qj \li1435\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin1435\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9963461\charrsid543100 \hich\af43\dbch\af11\loch\f43 +For more information about this feature, including instructions for turning the Automatic Updates off, go to }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4860412 \hich\af43\dbch\af11\loch\f43 + HYPERLINK "http://go.microsoft.com/fwlink/?LinkId=147032" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2706528 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b7400000068007400740070003a002f002f0067006f002e006d006900630072006f0073006f00660074002e0063006f006d002f00660077006c0069006e006b002f003f004c0069006e006b00490064003d0031003400 +37003000330032000000795881f43b1d7f48af2c825dc485276300000000a5ab000100002d0000ff000000001800000070000000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 \cs73\ul\cf2\insrsid12273991\charrsid7818577 \hich\af43\dbch\af11\loch\f43 +http://go.microsoft.com/fwlink/?LinkId=147032}}}\sectd \ltrsect\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9963461\charrsid543100 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid5464247 +\par {\listtext\pard\plain\ltrpar \s4 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid12273991\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard \ltrpar +\s4\qj \fi-360\li1440\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls31\outlinelevel3\adjustright\rin0\lin1440\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \ul\insrsid12273991\charrsid543100 \hich\af43\dbch\af11\loch\f43 +Web Content Features. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12273991\charrsid543100 \hich\af43\dbch\af11\loch\f43 + Features in the software can retrieve related content from Microsoft and provide it to you. To provide the content, these features send to Microsoft the type of operating system, name and version of the software you are using, type of browser and languag +\hich\af43\dbch\af11\loch\f43 e\hich\af43\dbch\af11\loch\f43 + code of the device where you installed the software. Examples of these features are clip art, templates, online training, online assistance, help and Appshelp. You may choose not to use these web content features. +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 ii.\tab}}\pard\plain \ltrpar\s3\qj \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid11616408 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af43 \ltrch\fcs0 \b\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 Use of Information.}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 We may \hich\af43\dbch\af11\loch\f43 use }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14762553 \hich\af43\dbch\af11\loch\f43 i}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid13566544 +\hich\af43\dbch\af11\loch\f43 nform\hich\af43\dbch\af11\loch\f43 ation collected about software use and performance }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 to}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid13566544 \hich\af43\dbch\af11\loch\f43 provide and}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 improve }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid13566544 \hich\af43\dbch\af11\loch\f43 Microsoft }{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 \hich\af43\dbch\af11\loch\f43 software and services}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid13566544 \hich\af43\dbch\af11\loch\f43 as further described in Microsoft\hich\f43 \rquote \loch\f43 +s Privacy Statement available at: }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid13566544\charrsid8605371 \hich\af43\dbch\af11\loch\f43 https://go.microsoft.com/fwlink/?LinkID=521839}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid4022173\charrsid543100 +\hich\af43\dbch\af11\loch\f43 . We may also share it with others, \hich\af43\dbch\af11\loch\f43 such as hardware and software vendors. They may use the information to improve how their products run with Microsoft software.}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid4022173 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid138238\charrsid543100 \hich\af43\dbch\af11\loch\f43 iii.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b\insrsid138238\charrsid543100 \hich\af43\dbch\af11\loch\f43 +Misuse of Internet-based Services.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid138238\charrsid543100 \hich\af43\dbch\af11\loch\f43 You may not use these services in any way that could harm them or impair anyone else\hich\f43 \rquote \loch\f43 s us +\hich\af43\dbch\af11\loch\f43 e of them. You may not use the services to try to gain unauthorized access to any service, data, account or network by any means. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid7541157 \hich\af43\dbch\af11\loch\f43 4.\tab}}\pard\plain \ltrpar\s1\qj \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid2560822 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid7541157 \hich\af43\dbch\af11\loch\f43 YOUR COMPLIANCE WITH PRIVACY AND DATA PROTECTION LAWS}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid5133814 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12140514 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid12140514 \hich\af43\dbch\af11\loch\f43 a.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid12793417 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12140514 \hich\af43\dbch\af11\loch\f43 Personal Information Definition. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid12140514\charrsid12793417 \hich\af43\dbch\af11\loch\f43 "Personal Information" means an\hich\af43\dbch\af11\loch\f43 +y information relating to an identified or identifiable natural person; an identifiable natural person is one who can be identified, directly or indirectly, in particular by reference to an identifier such as a name, an identification number, location dat +\hich\af43\dbch\af11\loch\f43 a\hich\af43\dbch\af11\loch\f43 , an online identifier or to one or more factors specific to the physical, physiological, genetic, mental, economic, cultural or social identity of that natural person.}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \b0\insrsid12140514\charrsid12140514 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 b.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 +Collecting Personal Information using Packaged}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6906187 \hich\af43\dbch\af11\loch\f43 and Add-on}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 APIs}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid9006310 \hich\af43\dbch\af11\loch\f43 . }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid5133814\charrsid12793417 \hich\af43\dbch\af11\loch\f43 If you use any API to collect }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid12140514\charrsid12793417 \hich\af43\dbch\af11\loch\f43 personal information}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid5133814\charrsid12793417 \hich\af43\dbch\af11\loch\f43 from the software, }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid7799211\charrsid12793417 \hich\af43\dbch\af11\loch\f43 you must comply with all laws and regulations applicable to your use of the data accessed through }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7799211\charrsid11867748 +\hich\af43\dbch\af11\loch\f43 APIs }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11867748 \hich\af43\dbch\af11\loch\f43 includin}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7799211\charrsid12793417 \hich\af43\dbch\af11\loch\f43 g without +\hich\af43\dbch\af11\loch\f43 limitation laws related to privacy, biometric data, data protection, and confidentiality of communications. Your use of the }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11867748 \hich\af43\dbch\af11\loch\f43 software}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7799211\charrsid12793417 \hich\af43\dbch\af11\loch\f43 is conditioned upon implementing and maintaining appropriate protections and measures for your}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11867748 +\hich\af43\dbch\af11\loch\f43 applications and services}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7799211\charrsid12793417 \hich\af43\dbch\af11\loch\f43 , a\hich\af43\dbch\af11\loch\f43 +nd that includes your responsibility to the data obtained through the use of }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11867748 \hich\af43\dbch\af11\loch\f43 APIs}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7799211\charrsid12793417 +\hich\af43\dbch\af11\loch\f43 . For the data you obtained through }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11867748 \hich\af43\dbch\af11\loch\f43 any APIs}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7799211\charrsid12793417 \hich\af43\dbch\af11\loch\f43 +, you must: }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid11867748 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid11867748 \hich\af43\dbch\af11\loch\f43 i.\tab}}\pard\plain \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid11867748 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11867748 \hich\af43\dbch\af11\loch\f43 +obtain all necessary consents before collecting and using data and only use the data for the limited purposes to wh\hich\af43\dbch\af11\loch\f43 ich the user consented, including any consent to changes in use}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid3289579 \hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11867748 \hich\af43\dbch\af11\loch\f43 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid11867748 \hich\af43\dbch\af11\loch\f43 ii.\tab}\hich\af43\dbch\af11\loch\f43 In the event you\hich\f43 \rquote \loch\f43 +re storing data, ensure that data is kept up to date and implement corrections, restrictions to data, or the deletion of data }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12850423 \hich\af43\dbch\af11\loch\f43 as updated through packaged }{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid9653457 \hich\af43\dbch\af11\loch\f43 or }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6168189 \hich\af43\dbch\af11\loch\f43 add-on }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12850423 \hich\af43\dbch\af11\loch\f43 APIs +\hich\af43\dbch\af11\loch\f43 or upon user request if required by applicable law}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid3289579 \hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11867748 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid11867748 \hich\af43\dbch\af11\loch\f43 iii.\tab}\hich\af43\dbch\af11\loch\f43 implement proper retention and deletion policies, including deleting all data when }{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid12850423 \hich\af43\dbch\af11\loch\f43 as directed by }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid3289579 \hich\af43\dbch\af11\loch\f43 your users or as required by applicable law; and}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid11867748 +\par {\listtext\pard\plain\ltrpar \s3 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid11867748 \hich\af43\dbch\af11\loch\f43 iv.\tab}}\pard \ltrpar\s3\ql \fi-357\li1077\ri0\sb120\sa120\widctlpar +\tx1077\jclisttab\tx1440\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl2\outlinelevel2\adjustright\rin0\lin1077\itap0\pararsid12793417 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11867748 \hich\af43\dbch\af11\loch\f43 +maintain and comply with a written statement ava\hich\af43\dbch\af11\loch\f43 ilable to your customers that describes your privacy practices regarding data and information you collect, use and that you share with any third parties.}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid5133814\charrsid11867748 \hich\af43\dbch\af11\loch\f43 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9006310\charrsid8210523 \hich\af43\dbch\af11\loch\f43 c.\tab}}\pard\plain \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid9006310 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 +\b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9006310\charrsid8210523 \hich\af43\dbch\af11\loch\f43 Location Framework. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 The software may contain a location framework component or APIs that enable su\hich\af43\dbch\af11\loch\f43 pport of location services in programs. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid7171325 \hich\af43\dbch\af11\loch\f43 Programs}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid1527996 \~}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 +that receive device location must comply with the requirements related to the Location Service APIs as described in the Microsoft Store Policies (}{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid12793417 +\hich\af43\dbch\af11\loch\f43 HYPERLINK "https://docs.microsoft.com/en-us/legal/windows/agreements/store-policies" }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid1527996\charrsid12793417 {\*\datafield +00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90baa000000680074007400700073003a002f002f0064006f00630073002e006d006900630072006f0073006f00660074002e0063006f006d002f0065006e002d00750073002f006c006500670061006c002f0077006900 +6e0064006f00770073002f00610067007200650065006d0065006e00740073002f00730074006f00720065002d0070006f006c00690063006900650073000000795881f43b1d7f48af2c825dc485276300000000a5ab0003000000}}}{\fldrslt {\rtlch\fcs1 \af43 \ltrch\fcs0 +\cs73\b0\ul\cf2\insrsid9006310\charrsid9006310 \hich\af43\dbch\af11\loch\f43 https://docs.microsoft.com/en-us/legal/windows/agreements/store-policies}}}\sectd \ltrsect +\psz1\linex0\headery0\footery0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid421856\sftnbj {\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid9006310 \hich\af43\dbch\af11\loch\f43 ). }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310 +\hich\af43\dbch\af11\loch\f43 I}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 f you choose to collect device location data }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310 \hich\af43\dbch\af11\loch\f43 +outside of\hich\af43\dbch\af11\loch\f43 the control }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid1451044 \hich\af43\dbch\af11\loch\f43 of Windows system s}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310 \hich\af43\dbch\af11\loch\f43 ettings, y}{ +\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 ou must }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310 \hich\af43\dbch\af11\loch\f43 obtain}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 legally sufficient consent for your data practices, and such practices}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid1527996 \~}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9006310\charrsid12793417 \hich\af43\dbch\af11\loch\f43 must comply with all other applicable laws and regulations.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9006310\charrsid1527996 \~}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9006310\charrsid12793417 +\par {\listtext\pard\plain\ltrpar \s2 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid3289579 \hich\af43\dbch\af11\loch\f43 d.\tab}}\pard \ltrpar\s2\ql \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls26\ilvl1\outlinelevel1\adjustright\rin0\lin720\itap0\pararsid12793417 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid3289579 \hich\af43\dbch\af11\loch\f43 Security. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid3289579\charrsid3289579 \hich\af43\dbch\af11\loch\f43 If your app}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid3289579 \hich\af43\dbch\af11\loch\f43 lication or service}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid3289579\charrsid3289579 +\hich\af43\dbch\af11\loch\f43 collects, stores or tr\hich\af43\dbch\af11\loch\f43 ansmits personal information, it must do so securely, by using modern cryptography methods.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9006310\charrsid3289579 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid6651166\charrsid543100 \hich\af43\dbch\af11\loch\f43 5.\tab}}\pard\plain \ltrpar\s1\qj \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid2560822 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6651166\charrsid543100 \hich\af43\dbch\af11\loch\f43 BACKUP COPY. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid6651166\charrsid12273991 \hich\af43\dbch\af11\loch\f43 +You may make one backup copy of the software. You may use it only to reinstall the software.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6651166\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid6651166\charrsid543100 \hich\af43\dbch\af11\loch\f43 6.\tab}\hich\af43\dbch\af11\loch\f43 DOCUMENTATION. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid6651166\charrsid12273991 \hich\af43\dbch\af11\loch\f43 Any person that has valid access to y\hich\af43\dbch\af11\loch\f43 our computer or internal network may copy and use the documentation for your internal, reference purposes}{\rtlch\fcs1 +\af43 \ltrch\fcs0 \b0\insrsid6576989\charrsid12273991 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6651166\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 7.\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 +SCOPE OF LICENSE. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9780096\charrsid12273991 \hich\af43\dbch\af11\loch\f43 The software is licensed, not sold. This agreement only gives you some rights to use the software. Microsoft reserves all +\hich\af43\dbch\af11\loch\f43 +other rights. Unless applicable law gives you more rights despite this limitation, you may use the software only as expressly permitted in this agreement. In doing so, you must comply with any technical limitations in the software that only allow you to u +\hich\af43\dbch\af11\loch\f43 s\hich\af43\dbch\af11\loch\f43 e it in certain ways. You may not}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid11226145\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s34\qj \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid11226145\charrsid543100 \hich\af43\dbch\af11\loch\f43 Except for the Microsoft .NET Framework, \hich\af43\dbch\af11\loch\f43 disclose to a third party the results of any benchmark test of the software}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid14762553 \hich\af43\dbch\af11\loch\f43 , with\hich\af43\dbch\af11\loch\f43 out first obtaining }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14762553\charrsid543100 \hich\af43\dbch\af11\loch\f43 Microsoft's prior written approval}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid16214760 \hich\af43\dbch\af11\loch\f43 ;}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid11226145\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 +work around any technical limitations in the s\hich\af43\dbch\af11\loch\f43 oftware; +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 +reverse engineer, decompile or disassemble the software, except and only to the extent that applicable law expressly permits, despite this limitation; +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 make more copies of the s\hich\af43\dbch\af11\loch\f43 +oftware than specified in this agreement or allowed by applicable law, despite this limitation; +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 publish the software for others to copy; +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 rent, lease or lend the software; +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 transfer the software or this agreement to any third party; or +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 use the softwa\hich\af43\dbch\af11\loch\f43 +re for commercial software hosting services. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 8.\tab}}\pard\plain \ltrpar\s1\qj \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid2560822 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 EXPORT RESTRICTIONS. }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid9780096\charrsid1574971 \hich\af43\dbch\af11\loch\f43 +The software is subject to United States export laws and regulations. You must comply with all domestic and international export laws and regulations that apply to the software. These law\hich\af43\dbch\af11\loch\f43 +s include restrictions on destinations, end users and end use. For additional information, see www.microsoft.com/exporting.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 9.\tab}\hich\af43\dbch\af11\loch\f43 SUPPORT SERVICES. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9780096\charrsid1073171 \hich\af43\dbch\af11\loch\f43 \hich\f43 Because this software is \'93\loch\f43 \hich\f43 as is,\'94\loch\f43 we may not provide support services for it.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 + +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 10.\tab}\hich\af43\dbch\af11\loch\f43 ENTIRE AGREEMENT. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9780096\charrsid1073171 \hich\af43\dbch\af11\loch\f43 This agreem\hich\af43\dbch\af11\loch\f43 +ent, and the terms for supplements, updates, Internet-based services and support services that you use, are the entire agreement for the software and support services.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid12793417 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid7541157 \hich\af43\dbch\af11\loch\f43 11.\tab}}\pard \ltrpar\s1\ql \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid7541157 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid7541157 \hich\af43\dbch\af11\loch\f43 INDEPENDENT PARTIES. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid7541157\charrsid12793417 \hich\af43\dbch\af11\loch\f43 Microsoft and you are independent contractors. Nothing in this\hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid7541157 \hich\af43\dbch\af11\loch\f43 a}{\rtlch\fcs1 +\af43 \ltrch\fcs0 \b0\insrsid7541157\charrsid12793417 \hich\af43\dbch\af11\loch\f43 greement shall be construed as creating an employer-employee relationship, processor-subprocessor relationship, a partnership, or a joint venture between the parties. + +\par {\*\bkmkstart OLE_LINK24}{\*\bkmkstart OLE_LINK23}{\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid14842076\charrsid14842076 \hich\af43\dbch\af11\loch\f43 12.\tab}}\pard \ltrpar +\s1\qj \fi-357\li357\ri0\sb120\sa120\widctlpar\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid14842076 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid14842076\charrsid14842076 +\hich\af43\dbch\af11\loch\f43 APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid14842076\charrsid12793417 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid4093123\charrsid12793417 {\*\bkmkend OLE_LINK24} +{\*\bkmkend OLE_LINK23}\hich\af43\dbch\af11\loch\f43 + If you acquired the software in the United States or Canada, the laws of the state or province where you live (or, if a business, where your principal place of business is located) govern the interpretation of this agreement, claims fo +\hich\af43\dbch\af11\loch\f43 +r its breach, and all other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of laws principles. If you acquired the software in any other country, its laws apply. If U.S. federal jurisdiction exists, you +\hich\af43\dbch\af11\loch\f43 \hich\af43\dbch\af11\loch\f43 +and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County, Washington for all disputes heard in court. If not, you and Microsoft consent to exclusive jurisdiction and venue in the Superior Court of King County, Washingt +\hich\af43\dbch\af11\loch\f43 o\hich\af43\dbch\af11\loch\f43 n for all disputes heard in court.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid14842076\charrsid12793417 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 13.\tab}}\pard \ltrpar\s1\qj \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid2560822 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 LEGAL EFFECT. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\b0\insrsid9780096\charrsid1073171 \hich\af43\dbch\af11\loch\f43 +This agreement describes certain legal rights. You may have other rights under the laws of your country. You may also have rights with respect to the party from whom you acquired the software. This agree\hich\af43\dbch\af11\loch\f43 +ment does not change your rights under the laws of your country if the laws of your country do not permit it to do so.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \b0\insrsid14437949 \hich\af43\dbch\af11\loch\f43 }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid9780096\charrsid543100 +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 14.\tab}\hich\af43\dbch\af11\loch\f43 DISCLAIMER OF WARRANTY. }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid9780096\charrsid14114137 \hich\af43\dbch\af11\loch\f43 \hich\f43 The software is licensed \'93\loch\f43 \hich\f43 as-is.\'94\loch\f43 You bear the risk of using it. Microsoft gives no express warranties, guar\hich\af43\dbch\af11\loch\f43 +antees or conditions. You may have additional consumer rights or statutory guarantees under your local laws which this agreement cannot change. To the extent permitted under your local laws, Microsoft excludes the implied warranties of merchantability, fi +\hich\af43\dbch\af11\loch\f43 t\hich\af43\dbch\af11\loch\f43 ness for a particular purpose and non-infringement. +\par }\pard\plain \ltrpar\qj \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 FOR AUSTRALIA \hich\f43 \endash \loch\f43 + You have statutory guarantees under the Australian Consumer Law and nothing in these terms is intended to affect those rights. +\par {\listtext\pard\plain\ltrpar \s1 \rtlch\fcs1 \ab\af43\afs20 \ltrch\fcs0 \b\f43\fs20\insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 15.\tab}}\pard\plain \ltrpar\s1\qj \fi-357\li357\ri0\sb120\sa120\widctlpar +\jclisttab\tx360\wrapdefault\aspalpha\aspnum\faauto\ls26\outlinelevel0\adjustright\rin0\lin357\itap0\pararsid2560822 \rtlch\fcs1 \ab\af43\afs19\alang1025 \ltrch\fcs0 \b\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 LIMITATION ON AND EXCLUSION OF REMEDIES AND DAMAGES. You \hich\af43\dbch\af11\loch\f43 +can recover from Microsoft and its suppliers only direct damages up to U.S. $5.00. You cannot recover any other damages, including consequential, lost profits, special, indirect or incidental damages. +\par }\pard\plain \ltrpar\qj \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid2560822 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 This limitation applies to +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar\s34\qj \fi-363\li720\ri0\sb120\sa120\widctlpar +\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls2\adjustright\rin0\lin720\itap0\pararsid11616408 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 \fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 anything related to the so\hich\af43\dbch\af11\loch\f43 ftware, services, content (including code) on third party Internet sites, or third party programs; and +\par {\listtext\pard\plain\ltrpar \s34 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\insrsid9780096\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}\hich\af43\dbch\af11\loch\f43 +claims for breach of contract, breach of warranty, guarantee or condition, strict liability, negligence, or other tort to the extent permitted by appli\hich\af43\dbch\af11\loch\f43 cable law. +\par }\pard\plain \ltrpar\qj \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid11616408 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 +It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your country may not allow the exclusion or limitation of incidental, consequential +\hich\af43\dbch\af11\loch\f43 or other damages. +\par }\pard \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 +\par \hich\af43\dbch\af11\loch\f43 Please note: As this software is distributed in Quebec, Canada, some of the clauses in this agreement are provided below in French. +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 \hich\f43 Remarque : Ce logiciel \'e9\loch\f43 \hich\f43 tant distribu\'e9\loch\f43 \hich\f43 au Qu\'e9\loch\f43 +bec, Canada, certaines des clauses dans ce contrat sont four\hich\af43\dbch\af11\loch\f43 \hich\f43 nies ci-dessous en fran\'e7\loch\f43 ais.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid2560822 +\par }\pard \ltrpar\qj \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid11616408 {\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 +\hich\f43 EXON\'c9\loch\f43 \hich\f43 RATION DE GARANTIE. Le logiciel vis\'e9\loch\f43 \hich\f43 par une licence est offert \'ab\loch\f43 \hich\f43 tel quel \'bb\loch\f43 \hich\f43 . Toute utilisation de ce logiciel est \'e0\loch\f43 \hich\f43 + votre seule risque et p\'e9\loch\f43 ril. Microsoft n\hich\f43 \rquote \loch\f43 \hich\f43 accorde aucune autre garantie expresse. Vous pouvez b\'e9\loch\f43 \hich\f43 n\'e9\loch\f43 ficier d\hich\af43\dbch\af11\loch\f43 \hich\f43 +e droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualit\'e9\loch\f43 marchande, d\hich\f43 \rquote \loch\f43 +\hich\f43 ad\'e9\loch\f43 \hich\f43 quation \'e0\loch\f43 un usage particulier et d\hich\f43 \rquote \loch\f43 abse\hich\af43\dbch\af11\loch\f43 n\hich\af43\dbch\af11\loch\f43 \hich\f43 ce de contrefa\'e7\loch\f43 on sont exclues.}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid2560822 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 \hich\f43 LIMITATION DES DOMMAGES-INT\'c9\loch\f43 \hich\f43 R\'ca\loch\f43 \hich\f43 TS ET EXCLUSION DE RESPONSABILIT\'c9 +\loch\f43 POUR LES DOMMAGES. Vous pouvez obtenir de Microsoft et de ses fournisseurs une \hich\af43\dbch\af11\loch\f43 \hich\f43 indemnisation en cas de dommages directs uniquement \'e0\loch\f43 \hich\f43 hauteur de 5,00 $ US. Vous ne pouvez pr\'e9 +\loch\f43 \hich\f43 tendre \'e0\loch\f43 \hich\f43 aucune indemnisation pour les autres dommages, y compris les dommages sp\'e9\loch\f43 \hich\f43 ciaux, indirects ou accessoires et pertes de b\'e9\loch\f43 \hich\f43 n\'e9\loch\f43 fices.}{\rtlch\fcs1 +\af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid2560822 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid2560822\charrsid543100 \hich\af43\dbch\af11\loch\f43 Crete}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 limitation }{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid1326355\charrsid543100 \hich\af43\dbch\af11\loch\f43 concern}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 : +\par {\listtext\pard\plain\ltrpar \s112 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}\pard\plain \ltrpar +\s112\qj \fi-360\li720\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ls20\adjustright\rin0\lin720\itap0\pararsid11616408\contextualspace \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 \hich\f43 tout ce qui est reli\'e9\loch\f43 + au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers ; et}{\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid2560822 +\par {\listtext\pard\plain\ltrpar \s112 \rtlch\fcs1 \af43\afs19 \ltrch\fcs0 \f3\fs19\lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \loch\af3\dbch\af11\hich\f3 \'b7\tab}}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 \hich\f43 les r\'e9\loch\f43 \hich\f43 clamations au titre de violation de contrat ou de garantie, ou au titre de responsabilit\'e9\loch\f43 +\hich\af43\dbch\af11\loch\f43 \hich\f43 stricte, de n\'e9\loch\f43 gligence ou d\hich\f43 \rquote \loch\f43 \hich\f43 une autre faute dans la limite autoris\'e9\loch\f43 e par la loi en vigueur.}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\lang1036\langfe1033\langnp1036\insrsid10820326\charrsid2560822 +\par }\pard\plain \ltrpar\qj \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0\pararsid11616408 \rtlch\fcs1 \af43\afs19\alang1025 \ltrch\fcs0 +\fs19\lang1033\langfe1033\loch\af43\hich\af43\dbch\af11\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 Elle s\hich\f43 \rquote \loch\f43 \hich\f43 +applique \'e9\loch\f43 \hich\f43 galement, m\'ea\loch\f43 \hich\f43 me si Microsoft connaissait ou devrait conna\'ee\loch\f43 tre l\hich\f43 \rquote \'e9\loch\f43 \hich\f43 ventualit\'e9\loch\f43 d\hich\f43 \rquote \loch\f43 +un tel dommage. Si votre pays n\hich\f43 \rquote \loch\f43 autorise pas l\hich\f43 \rquote \hich\af43\dbch\af11\loch\f43 \hich\f43 exclusion ou la limitation de responsabilit\'e9\loch\f43 + pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l\hich\f43 \rquote \loch\f43 exclusion ci-dessus ne s\hich\f43 \rquote \loch\f43 \hich\f43 appliquera pas \'e0\loch\f43 \hich\f43 votre \'e9 +\loch\f43 gard.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid2560822 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 \hich\f43 EFFET JURIDIQUE. Le pr\'e9\loch\f43 \hich\f43 sent contrat d\'e9\loch\f43 crit cert\hich\af43\dbch\af11\loch\f43 +ains droits juridiques. Vous pourriez avoir d\hich\f43 \rquote \loch\f43 \hich\f43 autres droits pr\'e9\loch\f43 \hich\f43 vus par les lois de votre pays. Le pr\'e9\loch\f43 \hich\f43 sent contrat ne modifie pas les droits que vous conf\'e8\loch\f43 +rent les lois de votre pays si celles-ci ne le permettent pas.}{\rtlch\fcs1 \af43 \ltrch\fcs0 \lang1036\langfe1033\langnp1036\insrsid10820326\charrsid2560822 +\par }\pard \ltrpar\ql \li90\ri0\sb120\sa120\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin90\itap0 {\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 *************** +\par \hich\af43\dbch\af11\loch\f43 EULAID:}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid742407\charrsid543100 \hich\af43\dbch\af11\loch\f43 WIN}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid3894335\charrsid543100 \hich\af43\dbch\af11\loch\f43 10}{\rtlch\fcs1 \af43 +\ltrch\fcs0 \insrsid742407\charrsid543100 \hich\af43\dbch\af11\loch\f43 SDK}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid3559666 \hich\af43\dbch\af11\loch\f43 RTM}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\insrsid10820326\charrsid543100 .}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6175996 \hich\af43\dbch\af11\loch\f43 AUG}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid3744016 \hich\af43\dbch\af11\loch\f43 _201}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid6175996 +\hich\af43\dbch\af11\loch\f43 8}{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid10820326\charrsid543100 \hich\af43\dbch\af11\loch\f43 _en-US +\par +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid2560822 +\par }{\rtlch\fcs1 \af43 \ltrch\fcs0 \insrsid9780096\charrsid543100 \hich\af43\dbch\af11\loch\f43 *************************************************************************}{\rtlch\fcs1 \af43 \ltrch\fcs0 +\lang1036\langfe1033\langnp1036\insrsid9780096\charrsid5722447 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210096b5ade296060000501b0000160000007468656d652f7468656d652f +7468656d65312e786d6cec594f6fdb3614bf0fd87720746f6327761a07758ad8b19b2d4d1bc46e871e698996d850a240d2497d1bdae38001c3ba618715d86d87 +615b8116d8a5fb34d93a6c1dd0afb0475292c5585e9236d88aad3e2412f9e3fbff1e1fa9abd7eec70c1d1221294fda5efd72cd4324f1794093b0eddd1ef62fad +79482a9c0498f184b4bd2991deb58df7dfbb8ad755446282607d22d771db8b944ad79796a40fc3585ee62949606ecc458c15bc8a702910f808e8c66c69b9565b +5d8a314d3c94e018c8de1a8fa94fd05093f43672e23d06af89927ac06762a049136785c10607758d9053d965021d62d6f6804fc08f86e4bef210c352c144dbab +999fb7b4717509af678b985ab0b6b4ae6f7ed9ba6c4170b06c788a705430adf71bad2b5b057d03606a1ed7ebf5babd7a41cf00b0ef83a6569632cd467faddec9 +699640f6719e76b7d6ac355c7c89feca9cccad4ea7d36c65b258a206641f1b73f8b5da6a6373d9c11b90c537e7f08dce66b7bbeae00dc8e257e7f0fd2badd586 +8b37a088d1e4600ead1ddaef67d40bc898b3ed4af81ac0d76a197c86826828a24bb318f3442d8ab518dfe3a20f000d6458d104a9694ac6d88728eee2782428d6 +0cf03ac1a5193be4cbb921cd0b495fd054b5bd0f530c1931a3f7eaf9f7af9e3f45c70f9e1d3ff8e9f8e1c3e3073f5a42ceaa6d9c84e5552fbffdeccfc71fa33f +9e7ef3f2d117d57859c6fffac327bffcfc793510d26726ce8b2f9ffcf6ecc98baf3efdfdbb4715f04d814765f890c644a29be408edf3181433567125272371be +15c308d3f28acd249438c19a4b05fd9e8a1cf4cd296699771c393ac4b5e01d01e5a30a787d72cf1178108989a2159c77a2d801ee72ce3a5c545a6147f32a9979 +3849c26ae66252c6ed637c58c5bb8b13c7bfbd490a75330f4b47f16e441c31f7184e140e494214d273fc80900aedee52ead87597fa824b3e56e82e451d4c2b4d +32a423279a668bb6690c7e9956e90cfe766cb37b077538abd27a8b1cba48c80acc2a841f12e698f13a9e281c57911ce298950d7e03aba84ac8c154f8655c4f2a +f074481847bd804859b5e696007d4b4edfc150b12addbecba6b18b148a1e54d1bc81392f23b7f84137c2715a851dd0242a633f900710a218ed715505dfe56e86 +e877f0034e16bafb0e258ebb4faf06b769e888340b103d3311da9750aa9d0a1cd3e4efca31a3508f6d0c5c5c398602f8e2ebc71591f5b616e24dd893aa3261fb +44f95d843b5974bb5c04f4edafb95b7892ec1108f3f98de75dc97d5772bdff7cc95d94cf672db4b3da0a6557f70db629362d72bcb0431e53c6066acac80d699a +6409fb44d08741bdce9c0e4971624a2378cceaba830b05366b90e0ea23aaa241845368b0eb9e2612ca8c742851ca251ceccc70256d8d87265dd96361531f186c +3d9058edf2c00eafe8e1fc5c509031bb4d680e9f39a3154de0accc56ae644441edd76156d7429d995bdd88664a9dc3ad50197c38af1a0c16d684060441db0256 +5e85f3b9660d0713cc48a0ed6ef7dedc2dc60b17e92219e180643ed27acffba86e9c94c78ab90980d8a9f0913ee49d62b512b79626fb06dccee2a432bbc60276 +b9f7dec44b7904cfbca4f3f6443ab2a49c9c2c41476dafd55c6e7ac8c769db1bc399161ee314bc2e75cf8759081743be1236ec4f4d6693e5336fb672c5dc24a8 +c33585b5fb9cc24e1d4885545b58463634cc5416022cd19cacfccb4d30eb45296023fd35a458598360f8d7a4003bbaae25e331f155d9d9a5116d3bfb9a95523e +51440ca2e0088dd844ec6370bf0e55d027a012ae264c45d02f708fa6ad6da6dce29c255df9f6cae0ec38666984b372ab5334cf640b37795cc860de4ae2816e95 +b21be5ceaf8a49f90b52a51cc6ff3355f47e0237052b81f6800fd7b802239daf6d8f0b1571a8426944fdbe80c6c1d40e8816b88b8569082ab84c36ff0539d4ff +6dce591a26ade1c0a7f669880485fd484582903d284b26fa4e2156cff62e4b9265844c4495c495a9157b440e091bea1ab8aaf7760f4510eaa69a6465c0e04ec6 +9ffb9e65d028d44d4e39df9c1a52ecbd3607fee9cec7263328e5d661d3d0e4f62f44acd855ed7ab33cdf7bcb8ae889599bd5c8b3029895b6825696f6af29c239 +b75a5bb1e6345e6ee6c28117e73586c1a2214ae1be07e93fb0ff51e133fb65426fa843be0fb515c187064d0cc206a2fa926d3c902e907670048d931db4c1a449 +59d366ad93b65abe595f70a75bf03d616c2dd959fc7d4e6317cd99cbcec9c58b34766661c7d6766ca1a9c1b327531486c6f941c638c67cd22a7f75e2a37be0e8 +2db8df9f30254d30c1372581a1f51c983c80e4b71ccdd28dbf000000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468 +656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4 +350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d2624 +52282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe5141 +73d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000 +0000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000 +000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019 +0200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210096b5ade296060000501b00001600000000 +000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027 +00000000000000000000000000a00900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d0100009b0a00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdlocked0 heading 1;\lsdqformat1 \lsdlocked0 heading 2; +\lsdqformat1 \lsdlocked0 heading 3;\lsdqformat1 \lsdlocked0 heading 4;\lsdqformat1 \lsdlocked0 heading 5;\lsdqformat1 \lsdlocked0 heading 6;\lsdqformat1 \lsdlocked0 heading 7;\lsdqformat1 \lsdlocked0 heading 8;\lsdqformat1 \lsdlocked0 heading 9; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority59 \lsdlocked0 Table Grid; +\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid; +\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1; +\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1; +\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; +\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2; +\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; +\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; +\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e50000000000000000000000004024 +603de93ed401feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} diff --git a/docs/Licenses/Windows-SDK/sdk_third_party_notices.rtf b/docs/Licenses/Windows-SDK/sdk_third_party_notices.rtf new file mode 100644 index 00000000..467d3cc0 --- /dev/null +++ b/docs/Licenses/Windows-SDK/sdk_third_party_notices.rtf @@ -0,0 +1,325 @@ +{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033\deflangfe1033{\fonttbl{\f0\fswiss\fprq2\fcharset0 Calibri;}{\f1\fswiss\fprq2\fcharset0 Tahoma;}} +{\colortbl ;\red0\green0\blue255;\red0\green0\blue0;\red255\green255\blue255;} +{\*\generator Riched20 10.0.15038}{\*\mmathPr\mdispDef1\mwrapIndent1440 }\viewkind4\uc1 +\pard\widctlpar\f0\fs22\par +CREDITS AND THIRD-PARTY SOFTWARE NOTICES AND INFORMATION\par +\par +This software is based on or incorporates material from the files listed below (collectively, \ldblquote Third Party Code\rdblquote ).\~\b Please Note\b0 : Microsoft is not the original author of the Third Party Code. The URL for the original file source, the original copyright notice and the license under which Microsoft received Third Party Code are set forth below together with the full text of such license. Such notices and license are provided solely for your information. Microsoft, not the third party, licenses this Third Party Code to you under the terms set forth in the Microsoft Software License Terms for the Microsoft Windows Software Development Kit for Windows 10 (the \ldblquote Microsoft Program\rdblquote ). Unless applicable law gives you more rights, Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise. \par +\par +The Microsoft Program includes files from certain third party software developed by their respective authors, as follows:\par +\par +BERKELEY YACC NOTICES AND INFORMATION BEGIN HERE\par +Upstream source {{\field{\*\fldinst{HYPERLINK http://dickey.his.com/byacc/byacc.html }}{\fldrslt{http://dickey.his.com/byacc/byacc.html\ul0\cf0}}}}\f0\fs22\par +=========================================\par +\par +Berkeley Yacc is in the public domain; changes made to it by the current\par +maintainer are likewise unrestricted. That applies to most of the files.\par +A few files (currently those related to autoconf scripting) have other\par +licenses as noted here.\par +\par +Current byacc upstream maintainer: Thomas Dickey \par +\par +Public domain notice and no warranty:\par +-------------------------------------------------------------------------------\par + Berkeley Yacc is an LALR(1) parser generator. Berkeley Yacc has been made\par +as compatible as possible with AT&T Yacc. Berkeley Yacc can accept any input\par +specification that conforms to the AT&T Yacc documentation. Specifications\par +that take advantage of undocumented features of AT&T Yacc will probably be\par +rejected.\par +\par + Berkeley Yacc is distributed with no warranty whatever. The code\par +is certain to contain errors. Neither the author nor any contributor\par +takes responsibility for any consequences of its use.\par +\par + Berkeley Yacc is in the public domain. The data structures and algorithms\par +used in Berkeley Yacc are all either taken from documents available to the\par +general public or are inventions of the author. Anyone may freely distribute\par +source or binary forms of Berkeley Yacc whether unchanged or modified.\par +Distributers may charge whatever fees they can obtain for Berkeley Yacc.\par +Programs generated by Berkeley Yacc may be distributed freely.\par +\par + Please report bugs to\par +\par +\tab\tab\tab robert.corbett@eng.Sun.COM\par +\par +Include a small example if possible. Please include the banner string from\par +skeleton.c with the bug report. Do not expect rapid responses.\par +=========================================\par +END OF BERKELEY YACC NOTICES AND INFORMATION\par +\par +ALLJOYN NOTICES AND INFORMATION BEGIN HERE\par +=========================================\par +%%AllSeen Alliance's AllJoyn files, available at {{\field{\*\fldinst{HYPERLINK "https://git.allseenalliance.org/gerrit/"}}{\fldrslt{\ul\cf1\cf1\ul https://git.allseenalliance.org/gerrit/#/admin/projects/}}}}\f0\fs22\par +\par +\'a9 2009-2014, AllSeen Alliance\par +\par + +\pard\widctlpar\sb144\sa288\sl408\slmult1 Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\par +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 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.\par + +\pard\widctlpar ALLJOYN NOTICES AND INFORMATION END HERE\par +=========================================\par +\par +DISCUTILS NOTICES AND INFORMATION BEGIN HERE\par + +\pard\widctlpar\sa200\sl276\slmult1\lang9 DiscUtils ({{\field{\*\fldinst{HYPERLINK http://discutils.codeplex.com/license }}{\fldrslt{http://discutils.codeplex.com/license\ul0\cf0}}}}\f0\fs22 )\par + +\pard\widctlpar\lang1033 =========================================\par + +\pard\widctlpar\sa200\sl276\slmult1\lang9 Copyright (c) 2008 Kenneth Bell\line\line Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\line\line The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\line\fs19\line THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\fs22\lang1033\par + +\pard\widctlpar =========================================\par +\par +END OF DISCUTILS NOTICES AND INFORMATION\par +\par + +\pard\widctlpar\sa200\sl276\slmult1 -------------------------------------------------------------------------\par +GM.DLS NOTICES AND INFORMATION BEGIN HERE\par +=========================================\par +GMREADME.TXT\par +Copyright (c) Microsoft Corporation.\~ All Rights Reserved.\par +------------\par +The GM.DLS file contains the Roland SoundCanvas Sound Set which is \par +protected under the following copyright: \par +Roland GS Sound Set/Microsoft (P) 1996 Roland Corporation U.S.\~ \par +The Roland SoundCanvas Sound Set is licensed under Microsoft's \par +End User License Agreement for use with Microsoft operating \par +system products only.\~ All other uses require a separate written \par +license from Roland.\par + +\pard\widctlpar =========================================\par +\par +END OF GM.DLS NOTICES AND INFORMATION\par +___________________________________________________\par +\par +BOOST NOTICES AND INFORMATION BEGIN HERE\par +\par +============================================\par + +\pard\cbpat3\widctlpar\sa100\sl252\slmult1\cf2\f1\fs20 Boost\fs18\par +\fs20 Provided for Informational Purposes Only\line\line Boost Software License - Version 1.0 - August 17th, 2003\line\line Permission is hereby granted, free of charge, to any person or organization\line obtaining a copy of the software and accompanying documentation covered by\line this license (the "Software") to use, reproduce, display, distribute,\line execute, and transmit the Software, and to prepare derivative works of the\line Software, and to permit third-parties to whom the Software is furnished to\line do so, all subject to the following:\line\line The copyright notices in the Software and this entire statement, including\line the above license grant, this restriction and the following disclaimer,\line must be included in all copies of the Software, in whole or in part, and\line all derivative works of the Software, unless such copies or derivative\line works are solely in the form of machine-executable object code generated by\line a source language processor.\line\line THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\line IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\line FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT\line SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE\line FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,\line ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\line DEALINGS IN THE SOFTWARE.\fs18\par +=========================================\par +END OF BOOST NOTICES AND INFORMATION\par +-------------------------------------------------------------------------\par +W3C NOTICES AND INFORMATION BEGIN HERE\par +=========================================\par +W3C\par +\par +Portions of this software are based in part on the work of W3C. Because Microsoft has included the W3C software in this product, Microsoft is required to include the following text that accompanied such software:\par +W3C \'ae SOFTWARE NOTICE AND LICENSE\par +{\cf0{\field{\*\fldinst{HYPERLINK http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 }}{\fldrslt{http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231\ul0\cf0}}}}\f1\fs18\par +This work (and included software, documentation such as READMEs, or other related items) is being provided by the copyright holders under the following license. By obtaining, using and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.\par +Permission to copy, modify, and distribute this software and its documentation, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the software and documentation or portions thereof, including modifications:\par +1.\tab The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. \par +2.\tab Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software Short Notice should be included (hypertext is preferred, text is permitted) within the body of any redistributed or derivative code. \par +3.\tab Notice of any changes or modifications to the files, including the date changes were made. (We recommend you provide URLs to the location from which the code is derived.) \par +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS", AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.\par +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.\par +The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the software without specific, written prior permission. Title to copyright in this software and any associated documentation will at all times remain with copyright holders.\par +\par +=========================================\par +END OF W3C NOTICES \par +\par +==============================================================================\par +Microsoft/DirectXShaderCompiler\par +\par +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION\par +\par +Do Not Translate or Localize\par +\par +This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise.\par +\par +LLVM\par +\par +==============================================================================\par +LLVM Release License\par +==============================================================================\par +University of Illinois/NCSA\par +Open Source License\par +\par +Copyright (c) 2003-2015 University of Illinois at Urbana-Champaign.\par +All rights reserved.\par +\par +Developed by:\par +\par + LLVM Team\par +\par + University of Illinois at Urbana-Champaign\par +\par + {{\field{\*\fldinst{HYPERLINK http://llvm.org }}{\fldrslt{http://llvm.org\ul0\cf0}}}}\f1\fs18\par +\par +Permission is hereby granted, free of charge, to any person obtaining a copy of\par +this software and associated documentation files (the "Software"), to deal with\par +the Software without restriction, including without limitation the rights to\par +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\par +of the Software, and to permit persons to whom the Software is furnished to do\par +so, subject to the following conditions:\par +\par + * Redistributions of source code must retain the above copyright notice,\par + this list of conditions and the following disclaimers.\par +\par + * Redistributions in binary form must reproduce the above copyright notice,\par + this list of conditions and the following disclaimers in the\par + documentation and/or other materials provided with the distribution.\par +\par + * Neither the names of the LLVM Team, University of Illinois at\par + Urbana-Champaign, nor the names of its contributors may be used to\par + endorse or promote products derived from this Software without specific\par + prior written permission.\par +\par +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\par +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\par +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\par +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\par +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\par +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE\par +SOFTWARE.\par +\par +==============================================================================\par +Copyrights and Licenses for Third Party Software Distributed with LLVM:\par +==============================================================================\par +The LLVM software contains code written by third parties. Such software will\par +have its own individual LICENSE.TXT file in the directory in which it appears.\par +This file will describe the copyrights, license, and restrictions which apply\par +to that code.\par +\par +The disclaimer of warranty in the University of Illinois Open Source License\par +applies to all code in the LLVM Distribution, and nothing in any of the\par +other licenses gives permission to use the names of the LLVM Team or the\par +University of Illinois to endorse or promote products derived from this\par +Software.\par +\par +The following pieces of software have additional or alternate copyrights,\par +licenses, and/or restrictions:\par +\par +\par +CLANG\par +\par +==============================================================================\par +LLVM Release License\par +==============================================================================\par +University of Illinois/NCSA\par +Open Source License\par +\par +Copyright (c) 2007-2015 University of Illinois at Urbana-Champaign.\par +All rights reserved.\par +\par +Developed by:\par +\par + LLVM Team\par +\par + University of Illinois at Urbana-Champaign\par +\par + {{\field{\*\fldinst{HYPERLINK http://llvm.org }}{\fldrslt{http://llvm.org\ul0\cf0}}}}\f1\fs18\par +\par +Permission is hereby granted, free of charge, to any person obtaining a copy of\par +this software and associated documentation files (the "Software"), to deal with\par +the Software without restriction, including without limitation the rights to\par +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\par +of the Software, and to permit persons to whom the Software is furnished to do\par +so, subject to the following conditions:\par +\par + * Redistributions of source code must retain the above copyright notice,\par + this list of conditions and the following disclaimers.\par +\par + * Redistributions in binary form must reproduce the above copyright notice,\par + this list of conditions and the following disclaimers in the\par + documentation and/or other materials provided with the distribution.\par +\par + * Neither the names of the LLVM Team, University of Illinois at\par + Urbana-Champaign, nor the names of its contributors may be used to\par + endorse or promote products derived from this Software without specific\par + prior written permission.\par +\par +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\par +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\par +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\par +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\par +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\par +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE\par +SOFTWARE.\par +\par +==============================================================================\par +The LLVM software contains code written by third parties. Such software will\par +have its own individual LICENSE.TXT file in the directory in which it appears.\par +This file will describe the copyrights, license, and restrictions which apply\par +to that code.\par +\par +The disclaimer of warranty in the University of Illinois Open Source License\par +applies to all code in the LLVM Distribution, and nothing in any of the\par +other licenses gives permission to use the names of the LLVM Team or the\par +University of Illinois to endorse or promote products derived from this\par +Software.\par +\par +\par +LLVM System Interface Library\par +-------------------------------------------------------------------------------\par +The LLVM System Interface Library is licensed under the Illinois Open Source\par +License and has the following additional copyright:\par +\par +Copyright (C) 2004 eXtensible Systems, Inc.\par +\par +OpenBSD regex\par +\par +$OpenBSD: COPYRIGHT,v 1.3 2003/06/02 20:18:36 millert Exp $\par +\par +Copyright 1992, 1993, 1994 Henry Spencer. All rights reserved.\par +This software is not subject to any license of the American Telephone\par +and Telegraph Company or of the Regents of the University of California.\par +\par +Permission is granted to anyone to use this software for any purpose on\par +any computer system, and to alter it and redistribute it, subject\par +to the following restrictions:\par +\par +1. The author is not responsible for the consequences of use of this\par + software, no matter how awful, even if they arise from flaws in it.\par +\par +2. The origin of this software must not be misrepresented, either by\par + explicit claim or by omission. Since few users ever read sources,\par + credits must appear in the documentation.\par +\par +3. Altered versions must be plainly marked as such, and must not be\par + misrepresented as being the original software. Since few users\par + ever read sources, credits must appear in the documentation.\par +\par +4. This notice may not be removed or altered.\par +\par +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\par +/*-\par + * Copyright (c) 1994\par + * The Regents of the University of California. All rights reserved.\par + *\par + * Redistribution and use in source and binary forms, with or without\par + * modification, are permitted provided that the following conditions\par + * are met:\par + * 1. Redistributions of source code must retain the above copyright\par + * notice, this list of conditions and the following disclaimer.\par + * 2. Redistributions in binary form must reproduce the above copyright\par + * notice, this list of conditions and the following disclaimer in the\par + * documentation and/or other materials provided with the distribution.\par + * 3. Neither the name of the University nor the names of its contributors\par + * may be used to endorse or promote products derived from this software\par + * without specific prior written permission.\par + *\par + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\par + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\par + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\par + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\par + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\par + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\par + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\par + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\par + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\par + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\par + * SUCH DAMAGE.\par + *\par + * @(#)COPYRIGHT 8.1 (Berkeley) 3/16/94\par + */\par +\par +END OF THIRD-PARTY NOTICES FOR Microsoft/DirectXShaderCompiler\par +==============================================================================\par +\par +\par +For additional Microsoft Third-Party Notices see: {\cf0{\field{\*\fldinst{HYPERLINK https://aka.ms/thirdpartynotices }}{\fldrslt{https://aka.ms/thirdpartynotices\ul0\cf0}}}}\f1\fs18\par +} + From b6c79f3d0162c204b56a62791e0d2f8a2560d5c5 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:33:58 +0100 Subject: [PATCH 011/316] Post-release appversion bump to 5.1.4-beta0 --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index e88f3275..012eb84d 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.1.3' +_static_appversion = '5.1.4-beta0' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' From 5f665bc67003755182f307daf3e8056ba5de565f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:43:00 +0100 Subject: [PATCH 012/316] Licenses: python-packages: pywin32 --- .../pywin32/Pythonwin/License.txt | 30 ++ .../pywin32/Pythonwin/Scintilla/License.txt | 20 + .../pywin32/adodbapi/license.txt | 505 ++++++++++++++++++ .../python-packages/pywin32/com/License.txt | 30 ++ .../python-packages/pywin32/win32/License.txt | 30 ++ 5 files changed, 615 insertions(+) create mode 100644 docs/Licenses/python-packages/pywin32/Pythonwin/License.txt create mode 100644 docs/Licenses/python-packages/pywin32/Pythonwin/Scintilla/License.txt create mode 100644 docs/Licenses/python-packages/pywin32/adodbapi/license.txt create mode 100644 docs/Licenses/python-packages/pywin32/com/License.txt create mode 100644 docs/Licenses/python-packages/pywin32/win32/License.txt diff --git a/docs/Licenses/python-packages/pywin32/Pythonwin/License.txt b/docs/Licenses/python-packages/pywin32/Pythonwin/License.txt new file mode 100644 index 00000000..fa340d74 --- /dev/null +++ b/docs/Licenses/python-packages/pywin32/Pythonwin/License.txt @@ -0,0 +1,30 @@ +Unless stated in the specfic source file, this work is +Copyright (c) 1994-2008, Mark Hammond +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 name of Mark Hammond nor the name of 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 REGENTS 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. diff --git a/docs/Licenses/python-packages/pywin32/Pythonwin/Scintilla/License.txt b/docs/Licenses/python-packages/pywin32/Pythonwin/Scintilla/License.txt new file mode 100644 index 00000000..36e64d92 --- /dev/null +++ b/docs/Licenses/python-packages/pywin32/Pythonwin/Scintilla/License.txt @@ -0,0 +1,20 @@ +License for Scintilla and SciTE + +Copyright 1998-2003 by Neil Hodgson + +All Rights Reserved + +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 appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation. + +NEIL HODGSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS, IN NO EVENT SHALL NEIL HODGSON 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. diff --git a/docs/Licenses/python-packages/pywin32/adodbapi/license.txt b/docs/Licenses/python-packages/pywin32/adodbapi/license.txt new file mode 100644 index 00000000..1e214b79 --- /dev/null +++ b/docs/Licenses/python-packages/pywin32/adodbapi/license.txt @@ -0,0 +1,505 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/docs/Licenses/python-packages/pywin32/com/License.txt b/docs/Licenses/python-packages/pywin32/com/License.txt new file mode 100644 index 00000000..6c1884e9 --- /dev/null +++ b/docs/Licenses/python-packages/pywin32/com/License.txt @@ -0,0 +1,30 @@ +Unless stated in the specfic source file, this work is +Copyright (c) 1996-2008, Greg Stein and Mark Hammond. +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 names of Greg Stein, Mark Hammond nor the name of 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 REGENTS 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. diff --git a/docs/Licenses/python-packages/pywin32/win32/License.txt b/docs/Licenses/python-packages/pywin32/win32/License.txt new file mode 100644 index 00000000..fa340d74 --- /dev/null +++ b/docs/Licenses/python-packages/pywin32/win32/License.txt @@ -0,0 +1,30 @@ +Unless stated in the specfic source file, this work is +Copyright (c) 1994-2008, Mark Hammond +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 name of Mark Hammond nor the name of 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 REGENTS 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. From ea636783933ceab4a65fcd7e0abadc15fc9b4016 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:44:42 +0100 Subject: [PATCH 013/316] Licenses: python-packages: certifi --- docs/Licenses/python-packages/certifi/LICENSE | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/Licenses/python-packages/certifi/LICENSE diff --git a/docs/Licenses/python-packages/certifi/LICENSE b/docs/Licenses/python-packages/certifi/LICENSE new file mode 100644 index 00000000..3c781983 --- /dev/null +++ b/docs/Licenses/python-packages/certifi/LICENSE @@ -0,0 +1 @@ +You are being redirected. From c61bc392593b6625edbdf3020d39038644581557 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:45:29 +0100 Subject: [PATCH 014/316] Licenses: python-packages: requests --- docs/Licenses/python-packages/requests/LICENSE | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/Licenses/python-packages/requests/LICENSE diff --git a/docs/Licenses/python-packages/requests/LICENSE b/docs/Licenses/python-packages/requests/LICENSE new file mode 100644 index 00000000..b27a23e6 --- /dev/null +++ b/docs/Licenses/python-packages/requests/LICENSE @@ -0,0 +1 @@ +You are being redirected. From 30c7829edadac7cb0f04ca39604819a928b50d22 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:46:49 +0100 Subject: [PATCH 015/316] Licenses: python-packages: watchdog --- docs/Licenses/python-packages/watchdog/COPYING | 1 + docs/Licenses/python-packages/watchdog/LICENSE | 1 + 2 files changed, 2 insertions(+) create mode 100644 docs/Licenses/python-packages/watchdog/COPYING create mode 100644 docs/Licenses/python-packages/watchdog/LICENSE diff --git a/docs/Licenses/python-packages/watchdog/COPYING b/docs/Licenses/python-packages/watchdog/COPYING new file mode 100644 index 00000000..74cc2115 --- /dev/null +++ b/docs/Licenses/python-packages/watchdog/COPYING @@ -0,0 +1 @@ +You are being redirected. diff --git a/docs/Licenses/python-packages/watchdog/LICENSE b/docs/Licenses/python-packages/watchdog/LICENSE new file mode 100644 index 00000000..2a2477a4 --- /dev/null +++ b/docs/Licenses/python-packages/watchdog/LICENSE @@ -0,0 +1 @@ +You are being redirected. From d5361aca41c11f22b756413a91eee583af82c476 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 15:48:15 +0100 Subject: [PATCH 016/316] Licenses: python-packages: semantic-version --- docs/Licenses/python-packages/semantic-version/LICENSE | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/Licenses/python-packages/semantic-version/LICENSE diff --git a/docs/Licenses/python-packages/semantic-version/LICENSE b/docs/Licenses/python-packages/semantic-version/LICENSE new file mode 100644 index 00000000..bc46d7d2 --- /dev/null +++ b/docs/Licenses/python-packages/semantic-version/LICENSE @@ -0,0 +1 @@ +You are being redirected. From 117a86e1c50aa27e10a2b8be3e7e370780e326e7 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 16:43:16 +0100 Subject: [PATCH 017/316] Licenses: python-packages: charset-normalizer --- docs/Licenses/python-packages/charset-normalizer/LICENSE | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/Licenses/python-packages/charset-normalizer/LICENSE diff --git a/docs/Licenses/python-packages/charset-normalizer/LICENSE b/docs/Licenses/python-packages/charset-normalizer/LICENSE new file mode 100644 index 00000000..52c1c5d5 --- /dev/null +++ b/docs/Licenses/python-packages/charset-normalizer/LICENSE @@ -0,0 +1 @@ +You are being redirected. From e6f741e74da350a6ab7066a93776ad80f73f064e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 16:51:13 +0100 Subject: [PATCH 018/316] Licenses: python-packages: urllib3 --- docs/Licenses/python-packages/urllib3/LICENSE.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/Licenses/python-packages/urllib3/LICENSE.txt diff --git a/docs/Licenses/python-packages/urllib3/LICENSE.txt b/docs/Licenses/python-packages/urllib3/LICENSE.txt new file mode 100644 index 00000000..8a4bdcbb --- /dev/null +++ b/docs/Licenses/python-packages/urllib3/LICENSE.txt @@ -0,0 +1 @@ +You are being redirected. From e5db795988c79f0ece09674d944db4bc41e2d0cc Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 16:55:02 +0100 Subject: [PATCH 019/316] Licenses: python-packages: idna --- docs/Licenses/python-packages/idna/LICENSE.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/Licenses/python-packages/idna/LICENSE.md diff --git a/docs/Licenses/python-packages/idna/LICENSE.md b/docs/Licenses/python-packages/idna/LICENSE.md new file mode 100644 index 00000000..a70597a1 --- /dev/null +++ b/docs/Licenses/python-packages/idna/LICENSE.md @@ -0,0 +1 @@ +You are being redirected. From fd215d47bfcd019f1c21cbb728be6e21a9258b28 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 17:17:39 +0100 Subject: [PATCH 020/316] ChangeLog: Call out `docs/Licenses` now we have it. --- ChangeLog.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index f2a879b7..e8031212 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,14 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first). --- +Copyrights +=== +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. + +--- + * We now test against, and package with, Python 3.9.6. **As a consequence of this we no longer support Windows 7. @@ -17,6 +25,8 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (it's not distributed with the Windows installer) for the currently used version in a given branch. +--- + Release 5.1.3 === From 1256a778f035bda5e2e14c4fec8822565dc018a5 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 17:22:50 +0100 Subject: [PATCH 021/316] Licenses: Correct WinSparkle, by following redirect --- docs/Licenses/WinSparkle/COPYING | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/Licenses/WinSparkle/COPYING b/docs/Licenses/WinSparkle/COPYING index 97a88c44..a9053772 100644 --- a/docs/Licenses/WinSparkle/COPYING +++ b/docs/Licenses/WinSparkle/COPYING @@ -1 +1,25 @@ -You are being redirected. +Copyright (c) 2009-2021 Vaclav Slavik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + + +This product includes software developed by the OpenSSL Project +for use in the OpenSSL Toolkit (http://www.openssl.org/). From f678811a7ee9021d3519ea1faffbde2169f95231 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 9 Aug 2021 17:26:15 +0100 Subject: [PATCH 022/316] Licenses: Correct for other github redirects on downloads --- docs/Licenses/py2exe/LICENSE.txt | 7 +- docs/Licenses/python-packages/certifi/LICENSE | 22 +- .../charset-normalizer/LICENSE | 22 +- docs/Licenses/python-packages/idna/LICENSE.md | 30 ++- .../Licenses/python-packages/requests/LICENSE | 176 ++++++++++++++- .../python-packages/semantic-version/LICENSE | 23 +- .../python-packages/urllib3/LICENSE.txt | 22 +- .../Licenses/python-packages/watchdog/COPYING | 15 +- .../Licenses/python-packages/watchdog/LICENSE | 203 +++++++++++++++++- 9 files changed, 511 insertions(+), 9 deletions(-) diff --git a/docs/Licenses/py2exe/LICENSE.txt b/docs/Licenses/py2exe/LICENSE.txt index 13187080..6706de1a 100644 --- a/docs/Licenses/py2exe/LICENSE.txt +++ b/docs/Licenses/py2exe/LICENSE.txt @@ -1 +1,6 @@ -You are being redirected. +The files source/MemoryModule.c and source/MemoryModule.h are licensed +under the Mozilla Public License Version 2.0, see +MPL2-License.txt. + +All other files are licensed under the MIT/X11 License, +see MIT-License.txt. diff --git a/docs/Licenses/python-packages/certifi/LICENSE b/docs/Licenses/python-packages/certifi/LICENSE index 3c781983..c2fda9a2 100644 --- a/docs/Licenses/python-packages/certifi/LICENSE +++ b/docs/Licenses/python-packages/certifi/LICENSE @@ -1 +1,21 @@ -You are being redirected. +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +Certificate data from Mozilla as of: Thu Nov 3 19:04:19 2011# +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1# +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/docs/Licenses/python-packages/charset-normalizer/LICENSE b/docs/Licenses/python-packages/charset-normalizer/LICENSE index 52c1c5d5..a86dd955 100644 --- a/docs/Licenses/python-packages/charset-normalizer/LICENSE +++ b/docs/Licenses/python-packages/charset-normalizer/LICENSE @@ -1 +1,21 @@ -You are being redirected. +MIT License + +Copyright (c) 2019 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/Licenses/python-packages/idna/LICENSE.md b/docs/Licenses/python-packages/idna/LICENSE.md index a70597a1..b6f87326 100644 --- a/docs/Licenses/python-packages/idna/LICENSE.md +++ b/docs/Licenses/python-packages/idna/LICENSE.md @@ -1 +1,29 @@ -You are being redirected. +BSD 3-Clause License + +Copyright (c) 2013-2021, Kim Davies +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder 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 HOLDER 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. diff --git a/docs/Licenses/python-packages/requests/LICENSE b/docs/Licenses/python-packages/requests/LICENSE index b27a23e6..67db8588 100644 --- a/docs/Licenses/python-packages/requests/LICENSE +++ b/docs/Licenses/python-packages/requests/LICENSE @@ -1 +1,175 @@ -You are being redirected. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/docs/Licenses/python-packages/semantic-version/LICENSE b/docs/Licenses/python-packages/semantic-version/LICENSE index bc46d7d2..66aba18c 100644 --- a/docs/Licenses/python-packages/semantic-version/LICENSE +++ b/docs/Licenses/python-packages/semantic-version/LICENSE @@ -1 +1,22 @@ -You are being redirected. +Copyright (c) The python-semanticversion project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. 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. + +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. diff --git a/docs/Licenses/python-packages/urllib3/LICENSE.txt b/docs/Licenses/python-packages/urllib3/LICENSE.txt index 8a4bdcbb..429a1767 100644 --- a/docs/Licenses/python-packages/urllib3/LICENSE.txt +++ b/docs/Licenses/python-packages/urllib3/LICENSE.txt @@ -1 +1,21 @@ -You are being redirected. +MIT License + +Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/Licenses/python-packages/watchdog/COPYING b/docs/Licenses/python-packages/watchdog/COPYING index 74cc2115..8eedbe98 100644 --- a/docs/Licenses/python-packages/watchdog/COPYING +++ b/docs/Licenses/python-packages/watchdog/COPYING @@ -1 +1,14 @@ -You are being redirected. +Copyright 2011 Yesudeep Mangalapilly +Copyright 2012 Google, Inc & contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/docs/Licenses/python-packages/watchdog/LICENSE b/docs/Licenses/python-packages/watchdog/LICENSE index 2a2477a4..d6456956 100644 --- a/docs/Licenses/python-packages/watchdog/LICENSE +++ b/docs/Licenses/python-packages/watchdog/LICENSE @@ -1 +1,202 @@ -You are being redirected. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 3326ac70038885abad9825600b81572f03991468 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 17:03:06 +0000 Subject: [PATCH 023/316] build(deps-dev): bump types-requests from 2.25.2 to 2.25.6 Bumps [types-requests](https://github.com/python/typeshed) from 2.25.2 to 2.25.6. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 95e81b51..02999609 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.1 mypy==0.910 pep8-naming==0.12.0 safety==1.10.3 -types-requests==2.25.2 +types-requests==2.25.6 # Code formatting tools autopep8==1.5.7 From b639623b4ae05a8b2cf2a5e2b2117dc44e26ae6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Aug 2021 17:03:15 +0000 Subject: [PATCH 024/316] build(deps-dev): bump pep8-naming from 0.12.0 to 0.12.1 Bumps [pep8-naming](https://github.com/PyCQA/pep8-naming) from 0.12.0 to 0.12.1. - [Release notes](https://github.com/PyCQA/pep8-naming/releases) - [Changelog](https://github.com/PyCQA/pep8-naming/blob/master/CHANGELOG.rst) - [Commits](https://github.com/PyCQA/pep8-naming/compare/0.12.0...0.12.1) --- updated-dependencies: - dependency-name: pep8-naming dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 95e81b51..e84364bb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -16,7 +16,7 @@ flake8-polyfill==1.0.2 flake8-use-fstring==1.1 mypy==0.910 -pep8-naming==0.12.0 +pep8-naming==0.12.1 safety==1.10.3 types-requests==2.25.2 From a00a6777466dfe388841e58491cf0fe02a3f7a2c Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 02:49:58 +0300 Subject: [PATCH 025/316] monitor.py: use trace instead debug to log CQC loadgames --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 02c1ed7f..f5fb2dfd 100644 --- a/monitor.py +++ b/monitor.py @@ -531,7 +531,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode').lower() == 'CQC': - logger.debug(f'loadgame to cqc: {entry}') + logger.trace(f'loadgame to cqc: {entry}') self.mode = 'CQC' else: self.mode = entry.get('GameMode') From 84500b775a37cce6f71e834e1ca07a400c508037 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 02:52:39 +0300 Subject: [PATCH 026/316] More paranoia about is journal_entry_cqc callable Co-authored-by: A_D --- plug.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plug.py b/plug.py index ec31ad54..f2a9b861 100644 --- a/plug.py +++ b/plug.py @@ -325,7 +325,7 @@ def notify_journal_entry_cqc(cmdr, is_beta, entry, state): error = None for plugin in PLUGINS: journal_entry = plugin._get_func('journal_entry_cqc') - if journal_entry: + if journal_entry is not None and callable(journal_entry): try: # Pass a copy of the journal entry in case the callee modifies it newerror = journal_entry(cmdr, is_beta, dict(entry), dict(state)) From 3283439485a24415f2a3d387e29df7cdd3aed89a Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 03:00:36 +0300 Subject: [PATCH 027/316] Code style fixes --- EDMarketConnector.py | 6 ++++-- monitor.py | 1 + plug.py | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index e8c567da..da75dd8b 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -872,8 +872,10 @@ class AppWindow(object): play_bad = False err: Optional[str] = None - if not monitor.cmdr or not monitor.mode or monitor.state['Captain'] or not monitor.system or \ - monitor.mode == 'CQC': + if ( + not monitor.cmdr or not monitor.mode or monitor.state['Captain'] + or not monitor.system or monitor.mode == 'CQC' + ): return # In CQC or on crew - do nothing if companion.session.state == companion.Session.STATE_AUTH: diff --git a/monitor.py b/monitor.py index f5fb2dfd..302d0931 100644 --- a/monitor.py +++ b/monitor.py @@ -533,6 +533,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode').lower() == 'CQC': logger.trace(f'loadgame to cqc: {entry}') self.mode = 'CQC' + else: self.mode = entry.get('GameMode') self.group = entry.get('Group') diff --git a/plug.py b/plug.py index f2a9b861..1287181d 100644 --- a/plug.py +++ b/plug.py @@ -330,8 +330,10 @@ def notify_journal_entry_cqc(cmdr, is_beta, entry, state): # Pass a copy of the journal entry in case the callee modifies it newerror = journal_entry(cmdr, is_beta, dict(entry), dict(state)) error = error or newerror + except Exception as e: logger.exception(f'Plugin "{plugin.name}" failed') + return error From e9ee65da143912c27a1e1a092c1840e023536b3d Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 03:15:50 +0300 Subject: [PATCH 028/316] Code style fix --- monitor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monitor.py b/monitor.py index 302d0931..61c77d04 100644 --- a/monitor.py +++ b/monitor.py @@ -536,6 +536,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below else: self.mode = entry.get('GameMode') + self.group = entry.get('Group') self.planet = None self.system = None From 7b823dd64d9168adb25152af2d5f83ee50aa7b6b Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 03:16:57 +0300 Subject: [PATCH 029/316] Remove commented out logger.debug() --- monitor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monitor.py b/monitor.py index 61c77d04..fe448537 100644 --- a/monitor.py +++ b/monitor.py @@ -494,7 +494,6 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below entry['timestamp'] # we expect this to exist # TODO: replace with assert? or an if key in check event_type = entry['event'].lower() - # logger.debug(f'Monitor event: {entry["event"]}') if event_type == 'fileheader': self.live = False From 6e2f49a63c43507f0d5ef8605beda4882c85208d Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:51:22 +0300 Subject: [PATCH 030/316] plug.py: remove useless 'as e' in notify_journal_entry_cqc --- plug.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plug.py b/plug.py index 1287181d..333d098c 100644 --- a/plug.py +++ b/plug.py @@ -331,7 +331,7 @@ def notify_journal_entry_cqc(cmdr, is_beta, entry, state): newerror = journal_entry(cmdr, is_beta, dict(entry), dict(state)) error = error or newerror - except Exception as e: + except Exception: logger.exception(f'Plugin "{plugin.name}" failed') return error From 030713f5b991451a3616d15a71ad9345e5fdf4d7 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:55:10 +0300 Subject: [PATCH 031/316] plug.py: rename journal_entry to cqc_callback in notify_journal_entry_cqc --- plug.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plug.py b/plug.py index 333d098c..334ca00d 100644 --- a/plug.py +++ b/plug.py @@ -324,11 +324,11 @@ def notify_journal_entry_cqc(cmdr, is_beta, entry, state): error = None for plugin in PLUGINS: - journal_entry = plugin._get_func('journal_entry_cqc') - if journal_entry is not None and callable(journal_entry): + cqc_callback = plugin._get_func('journal_entry_cqc') + if cqc_callback is not None and callable(cqc_callback): try: # Pass a copy of the journal entry in case the callee modifies it - newerror = journal_entry(cmdr, is_beta, dict(entry), dict(state)) + newerror = cqc_callback(cmdr, is_beta, dict(entry), dict(state)) error = error or newerror except Exception: From 42ce2ad41402f67f911ab85fad1d004b20a85eae Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:27:47 +0300 Subject: [PATCH 032/316] monitor.py: trace loadgames to CQC only if in trace_on --- monitor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index fe448537..88090970 100644 --- a/monitor.py +++ b/monitor.py @@ -530,7 +530,9 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode').lower() == 'CQC': - logger.trace(f'loadgame to cqc: {entry}') + if 'cqc-loadgame-events' in trace_on: + logger.trace(f'loadgame to cqc: {entry}') + self.mode = 'CQC' else: From 7b39b0fda042c3ab3a892820a08187bdfb25fdf9 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 15:55:14 +0300 Subject: [PATCH 033/316] plug.py: use copy.deepcopy() instead of dict() in cqc_callback To create deepcopy for passing data to cqc_callback --- plug.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plug.py b/plug.py index 334ca00d..02bd89d3 100644 --- a/plug.py +++ b/plug.py @@ -1,6 +1,7 @@ """ Plugin hooks for EDMC - Ian Norton, Jonathan Harris """ +import copy import importlib import logging import operator @@ -328,7 +329,7 @@ def notify_journal_entry_cqc(cmdr, is_beta, entry, state): if cqc_callback is not None and callable(cqc_callback): try: # Pass a copy of the journal entry in case the callee modifies it - newerror = cqc_callback(cmdr, is_beta, dict(entry), dict(state)) + newerror = cqc_callback(cmdr, is_beta, copy.deepcopy(entry), copy.deepcopy(state)) error = error or newerror except Exception: From ba38b5f02de65c5fcd6d4a0d49258ff22170c637 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 10 Aug 2021 16:16:01 +0300 Subject: [PATCH 034/316] plug.py: more verbose log message on failed while handling CQC mode journal entry --- plug.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plug.py b/plug.py index 02bd89d3..9c63b98c 100644 --- a/plug.py +++ b/plug.py @@ -333,7 +333,7 @@ def notify_journal_entry_cqc(cmdr, is_beta, entry, state): error = error or newerror except Exception: - logger.exception(f'Plugin "{plugin.name}" failed') + logger.exception(f'Plugin "{plugin.name}" failed while handling CQC mode journal entry') return error From 6328c47a41c8780fde84f49b9bdbe4dca99ddf5b Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 10 Aug 2021 14:59:34 +0100 Subject: [PATCH 035/316] Docs: Update Releasing.md and Automatic-Builds.md for using auto-builds We now prefer automatic builds, triggered off our normal `Release/` tags being pushed. --- docs/Automatic Builds.md | 42 +++++++---- docs/Releasing.md | 157 +++++++++++++++++++++++---------------- 2 files changed, 121 insertions(+), 78 deletions(-) diff --git a/docs/Automatic Builds.md b/docs/Automatic Builds.md index 11932c50..e709e79b 100644 --- a/docs/Automatic Builds.md +++ b/docs/Automatic Builds.md @@ -1,12 +1,36 @@ # Introduction -Instead of building manually as laid out by [Releasing](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md), you can build the EDMC installer using GitHub actions. +Instead of building manually as laid out by +[Releasing](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md), +you can build the EDMarketConnector installer using GitHub actions. ## Initiating a workflow run -Starting a workflow run is done from the Actions tab at the top of the main GitHub UI +### Automatically on tag push +Once you are sure you have a branch all ready for release as a new version you +should be adding a `Release/` tag at that HEAD. Pushing +such a tag to GitHub will cause the +[the GitHub Windows Build Action file](../.github/workflows/windows-build.yml) +to build an installer and create a draft release, with the pre-release box +pre-ticked. This ensures you don't accidentally create a new non-pre +release which will always become the target of the `latest` shortcut on GitHub. -NB: The branch you want to build must have the workflow file (`.github/workflows/windows-build.yml`), and the version of the file in that branch is the version that will be used for the build (e.g. for different python versions) +You can monitor such an auto-build from the Actions tab on GitHub. If it +completes successfully then check the Releases tab on GitHub for the draft +that was created. + +See [Releasing.md#Distribution](./Releasing.md#distribution) for details on +how to fully publish an automatic release so that running EDMarketConnector.exe +clients pick it up as an update. + +### Manually +Starting a workflow run is done from the Actions tab at the top of the main +GitHub UI + +NB: The branch you want to build must have the workflow file +(`.github/workflows/windows-build.yml`), and the version of the file in that +branch is the version that will be used for the build (e.g. for different +python versions) 1. Select the Actions tab at the top of the main GitHub interface 2. Select the `Build EDMC for Windows` workflow on the left @@ -16,7 +40,8 @@ NB: The branch you want to build must have the workflow file (`.github/workflows ## Downloading built installer files -When the workflow is (successfully) completed, it will upload the msi file it built as a "Workflow Artifact". You can find the artifacts as follows: +When the workflow is (successfully) completed, it will upload the msi file it +built as a "Workflow Artifact". You can find the artifacts as follows: 1. Select `All workflows` on the left 2. Select the `Build EDMC for Windows` action @@ -26,12 +51,3 @@ When the workflow is (successfully) completed, it will upload the msi file it bu Within the `Built Files` zip file is the installer msi **Please ensure you test the built msi before creating a release.** - -## Automatic release creation - -Github Actions can automatically create a release after finishing a build (as mentioned above). To make this happen, -simply push a tag to the repo with the format `v1.2.3` where 1.2.3 is the semver for the version (Note that this is -**DISTINCT** from the normal `Release/1.2.3` format for release tags). - -Once the push is completed, a build will start, and once that is complete, a draft release will be created. Edit the -release as needed and publish it. **Note that you should still test the built msi before publishing the release** diff --git a/docs/Releasing.md b/docs/Releasing.md index 3ef5d443..e96f4740 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -1,6 +1,15 @@ # Introduction -This document aims to enable anyone to quickly get up to speed on how to: +Builds can, **and must in normal operation** be run automatically from GitHub +Actions. For more information on that process +see [Automatic Builds](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Automatic%20Builds.md). +This allows us to state that the files we distribute never touched anything +but GitHub servers before a user downloaded them, which means no +possibility of malware on a developer's machine infecting the resulting files. + +Obviously you might still need to run a manual build on your own hardware +in order to test changes and/or diagnose build issues. As such +this document aims to enable anyone to quickly get up to speed on how to: 1. Build a Windows .exe for the application 1. Package that .exe into an .msi file for distribution @@ -10,7 +19,6 @@ This document aims to enable anyone to quickly get up to speed on how to: Note that for Windows only a 32-bit application is supported at this time. This is principally due to the Windows Registry handling in config.py. -Builds can be run automatically from GitHub Actions. For more information on that process, see [Automatic Builds](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Automatic%20Builds.md). # Environment @@ -96,16 +104,16 @@ that. 1. Application names, version and URL of the file with latest release information. These are all in the `config.py` file. See the `from config import ...` lines in setup.py. - 1. `appname`: The short appname, e.g. 'EDMarketConnector' - 1. `applongname`: The long appname, e.g. 'E:D Market Connector' - 1. `appcmdname`: The CLI appname, e.g. 'EDMC' - 1. `appversion`: The current version, e.g. `4.0.2`. **You MUST make - this something like `4.0.2+` to differentiate it from + 1. `appname`: The short appname, e.g. 'EDMarketConnector' + 2. `applongname`: The long appname, e.g. 'E:D Market Connector' + 3. `appcmdname`: The CLI appname, e.g. 'EDMC' + 4. `_static_appversion`: The current version, e.g. `4.0.2`. **You MUST + make this something like `4.0.2+` to differentiate it from upstream.** Whatever is in this field is what will be reported if sending messages to EDDN, or any of the third-party website APIs. This is utilising the 'build metadata' part of a Semantic version. - 1. `copyright`: The Copyright string. - 1. `update_feed`: The URL where the application looks for current latest + 5. `copyright`: The Copyright string. + 6. `update_feed`: The URL where the application looks for current latest version information. This URL should be hosting a renamed (so the full URL doesn't change over application versions) version of the appcast_win_.xml file. The original upstream value is @@ -214,19 +222,6 @@ a `stable` release, as well as any social media posts you make. update this first. 1. To be sure you include all the changes look at the git log since the prior appropriate (pre-)release. - 1. **Only if making a `stable` release.** Update `edmarketconnector.xml` to - add this changelog text to the correct section(s). - 1. Use the following to generate HTML from the MarkDown (`pip - install grip` first if you need to): - - grip ChangeLog.md --export ChangeLog.html - 1. If there's still a Mac OS section scroll down past it to the Windows - section. - 1. You'll need to change the `` and `<description>` texts to - reflect the latest version and the additional changelog. - 1. Update the `url` and `sparkle:version` elements of the `<enclosure>` - section. **NB: Yes, sparkle:version should be the Semantic Version - string, not the Windows A.B.C.D form.** 1. As you're working in a version-specific branch, `release-4.0.2`, you can safely commit these changes and push to GitHub. **Do not merge the branch with `releases` until the GitHub release is in place.** @@ -304,61 +299,92 @@ The git commit for this should end up being the release tag as below. # Distribution -Once you have tested the new .msi file: +Whether you built it manually or automatically you **MUST** test the `.msi` +installer file prior to making the release live. + +Once that is done then for manually built installers: 1. Add a git tag for the release, which you'll refer to when actually creating -the release: - 1. This should be named `Release/A.B.C`, e.g. `Release/4.0.2.` as per - the version string. + the release: + 1. This should be named `Release/A.B.C`, e.g. `Release/4.0.2.` as per + the version string. -1. Now push the release-specific branch to GitHub. - 1. Check which of your remotes is for github with `git remotes -v`. It - should really be `origin` and the following assumes that. - 1. `git push --set-upstream --tags origin release-4.0.2` + **Do NOT add this tag until you're sure you're ready. Pushing a tag to + GitHub that matches the pattern `Release/*` (double-check this in + [the GitHub Windows Build Action file](../.github/workflows/windows-build.yml)) + will cause an auto-build and creation of a draft release.** -1. Merge the release-specific branch into the appropriate `stable` or `beta` + Yes, this does mean you should really just be using this auto-build setup + when creating an installer for release to users. You'll at least need to + edit the draft release that it creates, i.e. swap out its `.msi` for the + one that you built. But, **again, you should just be using the auto-build + mechanism**. + +3. Now push the release-specific branch to GitHub. + 1. Check which of your remotes is for github with `git remotes -v`. It + should really be `origin` and the following assumes that. + 1. `git push --set-upstream --tags origin release-4.0.2` + +4. Merge the release-specific branch into the appropriate `stable` or `beta` branch. You can either do this locally and push the changes, or do it on GitHub. You'll want to reference `stable` or `beta` in the next step, *not the release-4.0.2 branch, as it's temporary.* -1. Craft a [new github Release](https://github.com/EDCD/EDMarketConnector/releases/new), +5. **You should no longer need to manually create a release, due to + auto-building of any release tag, but you'll probably still need to edit + in the ChangeLog, so...** + + Craft a [new github Release](https://github.com/EDCD/EDMarketConnector/releases/new), 1. Use the new tag so as to reference the correct commit, along with the appropriate `stable` or `beta` branch as the 'Target'. - 1. Use the changelog text you already prepared to fill in the 'Release + 2. Use the changelog text you already prepared to fill in the 'Release title' and description. - 1. Attach the `EDMarketConnector_win_<version>.msi` file for Windows (the + 3. Attach the `EDMarketConnector_win_<version>.msi` file for Windows (the Source Code files are added by github based on the release tag). - 1. **If you are making a `beta` or otherwise pre-release you MUST tick the + 4. **If you are making a `beta` or otherwise pre-release you MUST tick the `[ ] This is a pre-release` box.** Not doing so will cause this release - to be point to by the 'latest' URL. + to be pointed to by the 'latest' URL. + 5. We always create a discussion for any new release, so tick the + `Create a discussion for this release ` box, and check it's targeted at + the `Announcement` category. -1. **Check that the URL for the release that you specified in -`edmarketconnector.xml` actually matches where github has placed the `EDMarketConnector_win_<version>.msi` -file.** If, for instance, you fail to *update* this URL then upon running the -'new' installer it will silently fail, because you made people try to install -the old version over the old version. +Once the release is created, then **only if making a `stable` release** +update `edmarketconnector.xml` **in the `releases` branch only** to add this +changelog text to the correct section(s): +1. `git checkout releases` +2. `git merge stable` - You should have merged the new release branch + into `stable` above. +3. Use the following to generate HTML from the MarkDown (`pip + install grip` first if you need to): - Fix it if needs be and make a new commit. It's only in the `edmarketconnector.xml` - file so no need to redo the build. As this file is only important in the - `releases` branch you also don't need to redo the GitHub release, i.e. - the release tag doesn't need updating to point to this new commit. + grip --export ChangeLog.md +4. Open `edmarketconnector.xml` in your editor. +5. If there's still a Mac OS section croll down past it to the Windows + section. +6. You'll need to change the `<title>` and `<description>` texts to + reflect the latest version and the additional changelog. +7. Update the `url` and `sparkle:version` elements of the `<enclosure>` + section. + 1. The `url` needs to match what GitHub created in the Release for the + `.msi` file. Check it! -1. **Now merge the new release branch into `releases`.** -This is the final step that fully publishes the release for running -EDMC instances to pick up on 'Check for Updates'. The WinSparkle check for -updates specifically targets + If, for instance, you fail to *update* this URL then upon running the 'new' + installer it will silently fail, because you made people try to install + the old version over the old version. + 3. Yes, `sparkle:version` should be the Semantic Version string, + not the Windows A.B.C.D form. +8. `git push origin` - `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml` - - as per `config.py` `update_feed`. - 1. `git checkout releases` - 1. `git merge release-4.0.2` - 1. `git push origin` - - (Or merge on GitHub). - - NB: It can take some time for GitHub to show the changed - edmarketconnector.xml contents to all users. + This is the final step that fully publishes the release for running + EDMC instances to pick up on 'Check for Updates'. The WinSparkle check for + updates specifically targets: + + `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml` + + as per `config.py` `update_feed`. + + NB: It can take some time for GitHub to show the changed + edmarketconnector.xml contents to all users. **You should now update [Known Issues](https://github.com/EDCD/EDMarketConnector/issues/618) to reflect anything now fixed in latest release.** @@ -367,11 +393,12 @@ to reflect anything now fixed in latest release.** If you are making a pre-release then: -1. **DO NOT** Edit edmarketconnector.xml at all. No, not even if you think you - won't accidentally merge it into `releases`. Just don't change it at all. -1. **DO NOT** merge into `releases`. -1. **DO NOT** merge into `stable`. -1. *Do* merge the code into `beta` after you have made a 'pre-release' on +1. **DO NOT** Edit `edmarketconnector.xml` at all. No, not even if you + think you won't accidentally merge it into `releases`. Just don't change it + at all. +3. **DO NOT** merge into `releases`. +4. **DO NOT** merge into `stable`. +5. *Do* merge the code into `beta` after you have made a 'pre-release' on GitHub. # Changing Python version From 9027d7a4f121ede98e9ddcb1f259394b66ece3c6 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 10 Aug 2021 15:38:34 +0100 Subject: [PATCH 036/316] docs/Releasing: Also replace `hashes.sum` if overriding an auto build --- docs/Releasing.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/Releasing.md b/docs/Releasing.md index e96f4740..1ef33faa 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -305,9 +305,9 @@ installer file prior to making the release live. Once that is done then for manually built installers: 1. Add a git tag for the release, which you'll refer to when actually creating - the release: - 1. This should be named `Release/A.B.C`, e.g. `Release/4.0.2.` as per - the version string. + the release: + 1. This should be named `Release/A.B.C`, e.g. `Release/4.0.2.` as per + the version string. **Do NOT add this tag until you're sure you're ready. Pushing a tag to GitHub that matches the pattern `Release/*` (double-check this in @@ -316,8 +316,16 @@ Once that is done then for manually built installers: Yes, this does mean you should really just be using this auto-build setup when creating an installer for release to users. You'll at least need to - edit the draft release that it creates, i.e. swap out its `.msi` for the - one that you built. But, **again, you should just be using the auto-build + edit the draft release that it creates: + + 1. Swap out its `.msi` for the one that you built. + 2. Create a matching `hashes.sum` file for your `.msi` file: + + sha256sum EDMarketConnector_win*.msi > ./hashes.sum + + and replace the one in the draft release with this. + + But, **again, you should just be using the auto-build mechanism**. 3. Now push the release-specific branch to GitHub. From bd32b449037e33de75a958ec41162cdf70f8020d Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Wed, 11 Aug 2021 13:50:09 +0300 Subject: [PATCH 037/316] monitor.py: fix CQC to lower case --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 88090970..c436fbbf 100644 --- a/monitor.py +++ b/monitor.py @@ -529,7 +529,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # Odyssey: bool self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) - if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode').lower() == 'CQC': + if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode').lower() == 'cqc': if 'cqc-loadgame-events' in trace_on: logger.trace(f'loadgame to cqc: {entry}') From c08c1a574ec92981b9d5f38cd80c25826868cdd6 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Wed, 11 Aug 2021 13:55:46 +0300 Subject: [PATCH 038/316] plug.py: reorder notify* functions --- plug.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/plug.py b/plug.py index 9c63b98c..2899c237 100644 --- a/plug.py +++ b/plug.py @@ -273,6 +273,31 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state): return error +def notify_journal_entry_cqc(cmdr, is_beta, entry, state): + """ + Send a journal entry to each plugin. + :param cmdr: The Cmdr name, or None if not yet known + :param entry: The journal entry as a dictionary + :param state: A dictionary containing info about the Cmdr, current ship and cargo + :param is_beta: whether the player is in a Beta universe. + :returns: Error message from the first plugin that returns one (if any) + """ + + error = None + for plugin in PLUGINS: + cqc_callback = plugin._get_func('journal_entry_cqc') + if cqc_callback is not None and callable(cqc_callback): + try: + # Pass a copy of the journal entry in case the callee modifies it + newerror = cqc_callback(cmdr, is_beta, copy.deepcopy(entry), copy.deepcopy(state)) + error = error or newerror + + except Exception: + logger.exception(f'Plugin "{plugin.name}" failed while handling CQC mode journal entry') + + return error + + def notify_dashboard_entry(cmdr, is_beta, entry): """ Send a status entry to each plugin. @@ -313,31 +338,6 @@ def notify_newdata(data, is_beta): return error -def notify_journal_entry_cqc(cmdr, is_beta, entry, state): - """ - Send a journal entry to each plugin. - :param cmdr: The Cmdr name, or None if not yet known - :param entry: The journal entry as a dictionary - :param state: A dictionary containing info about the Cmdr, current ship and cargo - :param is_beta: whether the player is in a Beta universe. - :returns: Error message from the first plugin that returns one (if any) - """ - - error = None - for plugin in PLUGINS: - cqc_callback = plugin._get_func('journal_entry_cqc') - if cqc_callback is not None and callable(cqc_callback): - try: - # Pass a copy of the journal entry in case the callee modifies it - newerror = cqc_callback(cmdr, is_beta, copy.deepcopy(entry), copy.deepcopy(state)) - error = error or newerror - - except Exception: - logger.exception(f'Plugin "{plugin.name}" failed while handling CQC mode journal entry') - - return error - - def show_error(err): """ Display an error message in the status line of the main window. From 97998e6de9aa138a95b2b01c128b963298bbfa7f Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Wed, 11 Aug 2021 14:00:32 +0300 Subject: [PATCH 039/316] EDMarketConnector.py: clarify comments about CQC --- EDMarketConnector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index da75dd8b..9ead323c 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1184,8 +1184,8 @@ class AppWindow(object): return # in CQC if not entry['event'] or not monitor.mode: - # logger.trace('Startup or in CQC, returning') - return # Startup or in CQC + # logger.trace('Startup, returning') + return # Startup if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started: logger.info('Startup or LoadGame event') From 0bb06232b88a91cdb829ce7e14efb14af96bf0a3 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Wed, 11 Aug 2021 14:24:27 +0300 Subject: [PATCH 040/316] monitor.py: add default value to entry.get('GameMode') in CQC detection --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index c436fbbf..4f8802e8 100644 --- a/monitor.py +++ b/monitor.py @@ -529,7 +529,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # Odyssey: bool self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) - if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode').lower() == 'cqc': + if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode', '').lower() == 'cqc': if 'cqc-loadgame-events' in trace_on: logger.trace(f'loadgame to cqc: {entry}') From 97808f5561540372ca5c93e65210bb65ece1371e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 11 Aug 2021 12:33:22 +0100 Subject: [PATCH 041/316] monitor.py: Access trace_on via whole-config import This is necessary so as to see the same instance of config.trace_on as is set up by EDMarketConnector.py. --- monitor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/monitor.py b/monitor.py index 4f8802e8..c91c0567 100644 --- a/monitor.py +++ b/monitor.py @@ -18,8 +18,9 @@ from typing import Tuple if TYPE_CHECKING: import tkinter +import config as conf_module # Necessary to see the same config.trace_on as elsewhere import util_ships -from config import config, trace_on +from config import config from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised from EDMCLogging import get_main_logger @@ -518,7 +519,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.live = True # First event in 3.0 self.cmdr = entry['Name'] self.state['FID'] = entry['FID'] - if 'startup' in trace_on: + if 'startup' in conf_module.trace_on: logger.trace(f'"Commander" event, {monitor.cmdr=}, {monitor.state["FID"]=}') elif event_type == 'loadgame': @@ -530,7 +531,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode', '').lower() == 'cqc': - if 'cqc-loadgame-events' in trace_on: + if 'cqc-loadgame-events' in conf_module.trace_on: logger.trace(f'loadgame to cqc: {entry}') self.mode = 'CQC' @@ -569,7 +570,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if entry.get('Ship') is not None and self._RE_SHIP_ONFOOT.search(entry['Ship']): self.state['OnFoot'] = True - if 'startup' in trace_on: + if 'startup' in conf_module.trace_on: logger.trace(f'"LoadGame" event, {monitor.cmdr=}, {monitor.state["FID"]=}') elif event_type == 'newcommander': From 5ceac5ded75ccd43ad54e67b5461398358aeb5ad Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 11 Aug 2021 12:47:00 +0100 Subject: [PATCH 042/316] Contributing.md: Fix doc of how to use trace_on --- Contributing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Contributing.md b/Contributing.md index 50682e64..02e02b58 100644 --- a/Contributing.md +++ b/Contributing.md @@ -415,9 +415,9 @@ In addition to that we utilise one of the user-defined levels as: still need to include `--trace`. The code to check and log would be like: ```python - from config import trace_on + import config as conf_module # Necessary to see the same config.trace_on as elsewhere - if 'edsm-cmdr-events' in trace_on: + if 'edsm-cmdr-events' in conf_module.trace_on: logger.trace(f'De-queued ({cmdr=}, {entry["event"]=})') ``` From 505692052e8611a4347d3ebed8a7053cf65db092 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 11 Aug 2021 14:05:40 +0100 Subject: [PATCH 043/316] edsm: Update should_send() docstring for extra param --- plugins/edsm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index 30340925..9ad31c71 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -638,7 +638,7 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently else: logger.debug('Empty queue message, setting closing = True') closing = True # Try to send any unsent events before we close - entry = {'event': 'ShutDown'} # Dummy to allow for `entry['event']` below + entry = {'event': 'ShutDown'} # Dummy to allow for `uentry['event']` belowt retrying = 0 while retrying < 3: @@ -776,6 +776,7 @@ def should_send(entries: List[Mapping[str, Any]], event: str) -> bool: # noqa: Whether or not any of the given entries should be sent to EDSM. :param entries: The entries to check + :param event: The latest event being processed :return: bool indicating whether or not to send said entries """ # We MUST flush pending on logout, in case new login is a different Commander From 30048ded885938acdd9c4527360b4bdc40e71e76 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Wed, 11 Aug 2021 17:47:06 +0300 Subject: [PATCH 044/316] PLUGINS.md: initial update for `journal_entry_cqc` --- PLUGINS.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 5a7388b7..cf13d176 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -519,9 +519,9 @@ See [Avoiding potential pitfalls](#avoiding-potential-pitfalls). ### Events -Once you have created your plugin and EDMC has loaded it there are three other +Once you have created your plugin and EDMC has loaded it there are four other functions you can define to be notified by EDMC when something happens: -`journal_entry()`, `dashboard_entry()` and `cmdr_data()`. +`journal_entry()`, `journal_entry_cqc()`, `dashboard_entry()` and `cmdr_data()`. Your events all get called on the main Tkinter loop so be sure not to block for very long or the app will appear to freeze. If you have a long running @@ -697,6 +697,9 @@ 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).** + This event is not sent when EDMC is running on a different machine so you should not *rely* on receiving this event. @@ -737,6 +740,91 @@ Examples of this are: --- +--- + +### Journal entry CQC +New in version 5.1.3+ +```python +def journal_entry_cqc(cmdr: str, is_beta: bool, entry: Dict[str, Any], state: Dict[str, Any]) -> None: + if entry['event'] == 'Location': + # We loaded to CQC match, lets detect map! + cqc_maps = { # dict to map systems names to CQC maps + 'Bleae Aewsy GA-Y d1-14': 'Asteria Point', + 'Eta Cephei': 'Cluster Compound', + 'Theta Ursae Majoris': 'Elevate', + 'Boepp SU-E d12-818': 'Ice Field', + } + cqc_map = cqc_maps.get(entry['StarSystem']) + logger.info(f'Loaded to CQC map {cqc_map}') +``` + +This gets called when EDMC sees a new entry in the game's journal and we are currently in CQC. + +| Parameter | Type | Description | +| :-------- | :--------------: | :--------------------------------------------------------------------- | +| `cmdr` | `str` | Current commander name | +| `is_beta` | `bool` | Is the game currently in beta | +| `entry` | `Dict[str, Any]` | The journal event | +| `state` | `Dict[str, Any]` | More info about the commander, their ship, and their cargo (see below) | + +Content of `state` (updated to the current journal entry): + +| Field | Type | Description | +| :------------------- | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------- | +| `GameLanguage` | `Optional[str]` | `language` value from `Fileheader` event. | +| `GameVersion` | `Optional[str]` | `version` value from `Fileheader` event. | +| `GameBuild` | `Optional[str]` | `build` value from `Fileheader` event. | +| `Captain` | `Optional[str]` | Name of the commander who's crew you're on, if any | +| `Cargo` | `Optional[dict]` | Current cargo. Note that this will be totals, and any mission specific duplicates will be counted together | +| `CargoJSON` | `Optional[dict]` | content of cargo.json as of last read. | +| `Credits` | `int` | Current credits balance | +| `FID` | `str` | Frontier commander ID | +| `Horizons` | `bool` | From `LoadGame` event. | +| `Odyssey` | `bool` | From `LoadGame` event. `False` if not present, else the event value. | +| `Loan` | `Optional[int]` | Current loan amount, if any | +| `Raw` | `Optional[dict]` | Current raw engineering materials | +| `Manufactured` | `Optional[dict]` | Current manufactured engineering materials | +| `Encoded` | `Optional[dict]` | Current encoded engineering materials | +| `Component` | `Optional[dict]` | Current component materials | +| `Engineers` | `Optional[dict]` | Current Raw engineering materials | +| `Rank` | `Optional[Dict[str, Tuple[int, int]]` | Current ranks, each entry is a tuple of the current rank, and age | +| `Statistics` | `Optional[dict]` | Contents of a Journal Statistics event, ie, data shown in the stats panel. See the Journal manual for more info | +| `Role` | `Optional[str]` | Current role if in multi-crew, one of `Idle`, `FireCon`, `FighterCon` | +| `Friends` | `set` | Currently online friends | +| `ShipID` | `Optional[int]` | Frontier ID of current ship | +| `ShipIdent` | `Optional[str]` | Current user-set ship ID | +| `ShipName` | `Optional[str]` | Current user-set ship name | +| `ShipType` | `Optional[str]` | Internal name for the current ship type | +| `HullValue` | `Optional[int]` | Current ship value, excluding modules | +| `ModulesValue` | `Optional[int]` | Value of the current ship's modules | +| `Rebuy` | `Optional[int]` | Current ship's rebuy cost | +| `Modules` | `Optional[dict]` | Currently fitted modules | +| `NavRoute` | `Optional[dict]` | Last plotted multi-hop route | +| `ModuleInfo` | `Optional[dict]` | Last loaded ModulesInfo.json data | +| `OnFoot` | `Optional[bool]` | Whether the Cmdr is on foot | +| `Component` | `Optional[dict]` | 'Component' MicroResources in Odyssey, `int` count each. | +| `Item` | `Optional[dict]` | 'Item' MicroResources in Odyssey, `int` count each. | +| `Consumable` | `Optional[dict]` | 'Consumable' MicroResources in Odyssey, `int` count each. | +| `Data` | `Optional[dict]` | 'Data' MicroResources in Odyssey, `int` count each. | +| `BackPack` | `Optional[dict]` | `dict` of Odyssey MicroResources in backpack. | +| `BackpackJSON` | `Optional[dict]` | Content of Backpack.json as of last read. | +| `ShipLockerJSON` | `Optional[dict]` | Content of ShipLocker.json as of last read. | +| `SuitCurrent` | `Optional[dict]` | CAPI-returned data of currently worn suit. NB: May be `None` if no data. | +| `Suits` | `Optional[dict]`[1] | CAPI-returned data of owned suits. NB: May be `None` if no data. | +| `SuitLoadoutCurrent` | `Optional[dict]` | CAPI-returned data of current Suit Loadout. NB: May be `None` if no data. | +| `SuitLoadouts` | `Optional[dict]`[1] | CAPI-returned data of all Suit Loadouts. NB: May be `None` if no data. | +| `Taxi` | `Optional[bool]` | Whether or not we're currently in a taxi. NB: This is best effort with what the journals provide. | +| `Dropship` | `Optional[bool]` | Whether or not the above taxi is a Dropship | +| `Body` | `Optional[str]` | The body we're currently on / in the SOI of | +| `BodyType` | `Optional[str]` | The type of body that `Body` refers to | + +[1] - 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. +___ + ### Shutdown This gets called when the user closes the program: From 043fd4d8267a6e17d06db735b7099681525a8cda Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Thu, 12 Aug 2021 16:01:31 +0300 Subject: [PATCH 045/316] inara.py: put inara events trace logging under `inara-events` trace_on option --- plugins/inara.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/inara.py b/plugins/inara.py index ea46f9ce..48515856 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -22,6 +22,7 @@ from companion import CAPIData from config import applongname, appversion, config, debug_senders from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel +import config as conf_module # Necessary to see the same config.trace_on as elsewhere logger = get_main_logger() @@ -1491,7 +1492,9 @@ def new_worker(): ] } logger.info(f'sending {len(data["events"])} events for {creds.cmdr}') - logger.trace(f'Events:\n{json.dumps(data)}\n') + if 'inara-events' in conf_module.trace_on: + logger.trace(f'Events:\n{json.dumps(data)}\n') + try_send_data(TARGET_URL, data) time.sleep(WORKER_WAIT_TIME) From 41895d591a7b53593e07537ec2075a41d481ef7e Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 12 Aug 2021 16:45:18 +0200 Subject: [PATCH 046/316] added trace-if and TRACE_ALL loglevels --- EDMCLogging.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 6633064e..c6928a63 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -48,6 +48,7 @@ from threading import get_native_id as thread_native_id from traceback import print_exc from typing import TYPE_CHECKING, Tuple, cast +import config as config_mod from config import appcmdname, appname, config # TODO: Tests: @@ -77,8 +78,11 @@ _default_loglevel = logging.DEBUG # Define a TRACE level LEVEL_TRACE = 5 +LEVEL_TRACE_ALL = 3 logging.addLevelName(LEVEL_TRACE, "TRACE") +logging.addLevelName(LEVEL_TRACE_ALL, "TRACE_ALL") logging.TRACE = LEVEL_TRACE # type: ignore +logging.TRACE_ALL = LEVEL_TRACE_ALL # type: ignore logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type: ignore logging.TRACE, # type: ignore message, @@ -86,6 +90,21 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type **kwargs ) + +def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None: + if condition not in config_mod.trace_on: + self._log(logging.TRACE_ALL, message, args, **kwargs) # type: ignore # we added it + + else: + # its in there, it gets to end up in regular TRACE + self._log(logging.TRACE, message, args, **kwargs) # type: ignore # we added it + + +logging.Logger.trace_if = _trace_if # type: ignore + +# we cant hide this from `from xxx` imports and I'd really rather no-one other than `logging` had access to it +del _trace_if + if TYPE_CHECKING: from types import FrameType @@ -98,6 +117,13 @@ if TYPE_CHECKING: """Fake trace method.""" return self._log(LEVEL_TRACE, message, args, **kwargs) + def trace_if(self, condition: str, message, *args, **kwargs) -> None: + """Fake trace if method, traces only if condition exists in trace_on.""" + if condition in config_mod.trace_on: + return self._log(LEVEL_TRACE, message, *args, **kwargs) + else: + return self._log(LEVEL_TRACE_ALL, message, *args, **kwargs) + class Logger: """ @@ -492,7 +518,7 @@ class EDMCContextFilter(logging.Filter): return module_name -def get_main_logger() -> 'LoggerMixin': +def get_main_logger(sublogger_name: str = '') -> 'LoggerMixin': """Return the correct logger for how the program is being run.""" if not os.getenv("EDMC_NO_UI"): # GUI app being run From 03d90daedcd236404caa4d562476b9ee8beb5a70 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 12 Aug 2021 16:46:04 +0200 Subject: [PATCH 047/316] added support for --trace-all argument to ignore trace-if directives --- EDMarketConnector.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 9ead323c..259fcce3 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -76,6 +76,12 @@ if __name__ == '__main__': # noqa: C901 action='store_true', ) + parser.add_argument( + "--trace-all", + help="Disable trace-on functionality", + action='store_true' + ) + parser.add_argument( '--reset-ui', help='reset UI theme and transparency to defaults', @@ -123,9 +129,16 @@ if __name__ == '__main__': # noqa: C901 args = parser.parse_args() + level_to_set: Optional[int] = None if args.trace: - logger.setLevel(logging.TRACE) - edmclogger.set_channels_loglevel(logging.TRACE) + level_to_set = logging.TRACE # type: ignore # it exists + + elif args.trace_all: + level_to_set = logging.TRACE_ALL # type: ignore # it exists + + if level_to_set is not None: + logger.setLevel(level_to_set) + edmclogger.set_channels_loglevel(level_to_set) if args.force_localserver_for_auth: config.set_auth_force_localserver() From 80fbfd17805834b2b8f966171beeafb576a936e5 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 12 Aug 2021 16:46:26 +0200 Subject: [PATCH 048/316] Updated docs containing info about trace-if --- Contributing.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Contributing.md b/Contributing.md index 02e02b58..54e39907 100644 --- a/Contributing.md +++ b/Contributing.md @@ -409,20 +409,27 @@ In addition to that we utilise one of the user-defined levels as: selected from Settings in the UI. As well as just using bare `logger.trace(...)` you can also gate it to only - log if asked to at invocation time by utilising the `--trace-on ...` + log if asked to at invocation time by utilising the `--trace-on ...` command-line argument. e.g. `EDMarketConnector.py --trace --trace-on edsm-cmdr-events`. Note how you - still need to include `--trace`. The code to check and log would be like: + still need to include `--trace`. + + `--trace-on` stores its arguments in `config.trace_on`. + To make use of `--trace-on`, you can either check `config.trace_on` yourself: + + ```python + import config + if 'my-trace-rule' in config.trace_on: + logger.trace('my log message') + ``` + + or you can use the helper method provided on `logger`: ```python - import config as conf_module # Necessary to see the same config.trace_on as elsewhere + logger.trace_if('my-trace-rule', 'my-log-message') + ``` - if 'edsm-cmdr-events' in conf_module.trace_on: - logger.trace(f'De-queued ({cmdr=}, {entry["event"]=})') - ``` - - This way you can set up TRACE logging that won't spam just because of - `--trace` being used. + This way you can set up TRACE logging that won't spam just because `--trace` is used. --- From fefa3d351d98f8ab0782c9906f09d8138742a464 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 12 Aug 2021 16:46:55 +0200 Subject: [PATCH 049/316] replaced if x in trace_on with trace_if --- monitor.py | 11 +++--- plugins/edsm.py | 91 ++++++++++++++++++++++--------------------------- 2 files changed, 44 insertions(+), 58 deletions(-) diff --git a/monitor.py b/monitor.py index c91c0567..7ac856dd 100644 --- a/monitor.py +++ b/monitor.py @@ -25,6 +25,7 @@ from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised from EDMCLogging import get_main_logger logger = get_main_logger() +STARTUP = 'startup' if TYPE_CHECKING: def _(x: str) -> str: @@ -519,8 +520,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.live = True # First event in 3.0 self.cmdr = entry['Name'] self.state['FID'] = entry['FID'] - if 'startup' in conf_module.trace_on: - logger.trace(f'"Commander" event, {monitor.cmdr=}, {monitor.state["FID"]=}') + logger.trace_if(STARTUP, f'"Commander" event, {monitor.cmdr=}, {monitor.state["FID"]=}') elif event_type == 'loadgame': # Odyssey Release Update 5 -- This contains data that doesn't match the format used in FileHeader above @@ -531,9 +531,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode', '').lower() == 'cqc': - if 'cqc-loadgame-events' in conf_module.trace_on: - logger.trace(f'loadgame to cqc: {entry}') - + logger.trace_if('cqc-loadgame-events', f'loadgame to cqc: {entry}') self.mode = 'CQC' else: @@ -570,8 +568,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if entry.get('Ship') is not None and self._RE_SHIP_ONFOOT.search(entry['Ship']): self.state['OnFoot'] = True - if 'startup' in conf_module.trace_on: - logger.trace(f'"LoadGame" event, {monitor.cmdr=}, {monitor.state["FID"]=}') + logger.trace_if(STARTUP, f'"LoadGame" event, {monitor.cmdr=}, {monitor.state["FID"]=}') elif event_type == 'newcommander': self.cmdr = entry['Name'] diff --git a/plugins/edsm.py b/plugins/edsm.py index 9ad31c71..65e8012e 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -38,6 +38,10 @@ EDSM_POLL = 0.1 _TIMEOUT = 20 DISCARDED_EVENTS_SLEEP = 10 +# trace-if events +CMDR_EVENTS = 'edsm-cmdr-events' +LOCATION_EVENTS = 'edsm-locations' + class This: """Holds module globals.""" @@ -360,8 +364,7 @@ def credentials(cmdr: str) -> Optional[Tuple[str, str]]: :param cmdr: The commander to get credentials for :return: The credentials, or None """ - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'{cmdr=}') + logger.trace_if(CMDR_EVENTS, f'{cmdr=}') # Credentials for cmdr if not cmdr: @@ -380,15 +383,12 @@ def credentials(cmdr: str) -> Optional[Tuple[str, str]]: if idx >= len(edsm_usernames) or idx >= len(edsm_apikeys): return None - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'{cmdr=}: returning ({edsm_usernames[idx]=}, {edsm_apikeys[idx]=})') + logger.trace_if(CMDR_EVENTS, f'{cmdr=}: returning ({edsm_usernames[idx]=}, {edsm_apikeys[idx]=})') return (edsm_usernames[idx], edsm_apikeys[idx]) else: - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'{cmdr=}: returning None') - + logger.trace_if(CMDR_EVENTS, f'{cmdr=}: returning None') return None @@ -515,8 +515,7 @@ def journal_entry( # noqa: C901, CCR001 'Encoded': [{'Name': k, 'Count': v} for k, v in state['Encoded'].items()], } materials.update(transient) - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'"LoadGame" event, queueing Materials: {cmdr=}') + logger.trace_if(CMDR_EVENTS, f'"LoadGame" event, queueing Materials: {cmdr=}') this.queue.put((cmdr, materials)) @@ -524,8 +523,7 @@ def journal_entry( # noqa: C901, CCR001 # logger.trace(f'''{entry["event"]} # Queueing: {entry!r}''' # ) - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'"{entry["event"]=}" event, queueing: {cmdr=}') + logger.trace_if(CMDR_EVENTS, f'"{entry["event"]=}" event, queueing: {cmdr=}') this.queue.put((cmdr, entry)) @@ -632,8 +630,7 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently item: Optional[Tuple[str, Mapping[str, Any]]] = this.queue.get() if item: (cmdr, entry) = item - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'De-queued ({cmdr=}, {entry["event"]=})') + logger.trace_if(CMDR_EVENTS, f'De-queued ({cmdr=}, {entry["event"]=})') else: logger.debug('Empty queue message, setting closing = True') @@ -649,34 +646,30 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently break try: if item and entry['event'] not in this.discarded_events: - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'({cmdr=}, {entry["event"]=}): not in discarded_events, appending to pending') + logger.trace_if( + CMDR_EVENTS, f'({cmdr=}, {entry["event"]=}): not in discarded_events, appending to pending') pending.append(entry) if pending and should_send(pending, entry['event']): - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'({cmdr=}, {entry["event"]=}): should_send() said True') - pendings = [f"{p}\n" for p in pending] - logger.trace(f'pending contains:\n{pendings}') + logger.trace_if(CMDR_EVENTS, f'({cmdr=}, {entry["event"]=}): should_send() said True') + logger.trace_if(CMDR_EVENTS, f'pending contains:\n{chr(0x0A).join(str(p) for p in pending)}') - if 'edsm-locations' in trace_on and \ - any(p for p in pending if p['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked')): - logger.trace("pending has at least one of " - "('CarrierJump', 'FSDJump', 'Location', 'Docked')" - " and it passed should_send()") + if any(p for p in pending if p['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked')): + logger.trace_if(LOCATION_EVENTS, "pending has at least one of " + "('CarrierJump', 'FSDJump', 'Location', 'Docked')" + " and it passed should_send()") for p in pending: if p['event'] in ('Location'): - logger.trace('"Location" event in pending passed should_send(), ' - f'timestamp: {p["timestamp"]}') + logger.trace_if(LOCATION_EVENTS, '"Location" event in pending passed should_send(), ' + f'timestamp: {p["timestamp"]}') creds = credentials(cmdr) # TODO: possibly unbound if creds is None: raise ValueError("Unexpected lack of credentials") username, apikey = creds - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'({cmdr=}, {entry["event"]=}): Using {username=} from credentials()') + logger.trace_if(CMDR_EVENTS, f'({cmdr=}, {entry["event"]=}): Using {username=} from credentials()') data = { 'commanderName': username.encode('utf-8'), @@ -686,23 +679,26 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently 'message': json.dumps(pending, ensure_ascii=False).encode('utf-8'), } - if 'edsm-locations' in trace_on and \ - any(p for p in pending if p['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked')): + if any(p for p in pending if p['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked')): data_elided = data.copy() data_elided['apiKey'] = '<elided>' - logger.trace( - "pending has at least one of " - "('CarrierJump', 'FSDJump', 'Location', 'Docked')" + logger.trace_if( + LOCATION_EVENTS, + "pending has at least one of ('CarrierJump', 'FSDJump', 'Location', 'Docked')" " Attempting API call with the following events:" ) for p in pending: - logger.trace(f"Event: {p!r}") + logger.trace_if(LOCATION_EVENTS, f"Event: {p!r}") if p['event'] in ('Location'): - logger.trace('Attempting API call for "Location" event with timestamp: ' - f'{p["timestamp"]}') + logger.trace_if( + LOCATION_EVENTS, + f'Attempting API call for "Location" event with timestamp: {p["timestamp"]}' + ) - logger.trace(f'Overall POST data (elided) is:\n{data_elided}') + logger.trace_if( + LOCATION_EVENTS, f'Overall POST data (elided) is:\n{json.dumps(data_elided, indent=2)}' + ) r = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT) # logger.trace(f'API response content: {r.content}') @@ -761,8 +757,7 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently if entry['event'].lower() in ('shutdown', 'commander', 'fileheader'): # Game shutdown or new login so we MUST not hang on to pending pending = [] - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'Blanked pending because of event: {entry["event"]}') + logger.trace_if(CMDR_EVENTS, f'Blanked pending because of event: {entry["event"]}') if closing: logger.debug('closing, so returning.') @@ -781,8 +776,7 @@ def should_send(entries: List[Mapping[str, Any]], event: str) -> bool: # noqa: """ # We MUST flush pending on logout, in case new login is a different Commander if event.lower() in ('shutdown', 'fileheader'): - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'True because {event=}') + logger.trace_if(CMDR_EVENTS, f'True because {event=}') return True @@ -791,8 +785,7 @@ def should_send(entries: List[Mapping[str, Any]], event: str) -> bool: # noqa: if entries and entries[-1]['event'] == 'Scan': this.navbeaconscan -= 1 if this.navbeaconscan: - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'False because {this.navbeaconscan=}') + logger.trace_if(CMDR_EVENTS, f'False because {this.navbeaconscan=}') return False @@ -808,8 +801,7 @@ def should_send(entries: List[Mapping[str, Any]], event: str) -> bool: # noqa: # Cargo is the last event on startup, unless starting when docked in which case Docked is the last event this.newgame = False this.newgame_docked = False - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'True because {entry["event"]=}') + logger.trace_if(CMDR_EVENTS, f'True because {entry["event"]=}') return True @@ -820,17 +812,14 @@ def should_send(entries: List[Mapping[str, Any]], event: str) -> bool: # noqa: 'CommunityGoal', # Spammed periodically 'ModuleBuy', 'ModuleSell', 'ModuleSwap', # will be shortly followed by "Loadout" 'ShipyardBuy', 'ShipyardNew', 'ShipyardSwap'): # " - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'True because {entry["event"]=}') + logger.trace_if(CMDR_EVENTS, f'True because {entry["event"]=}') return True else: - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'{entry["event"]=}, {this.newgame_docked=}') + logger.trace_if(CMDR_EVENTS, f'{entry["event"]=}, {this.newgame_docked=}') - if 'edsm-cmdr-events' in trace_on: - logger.trace(f'False as default: {this.newgame_docked=}') + logger.trace_if(CMDR_EVENTS, f'False as default: {this.newgame_docked=}') return False From 05e6838637e4292736c7430fb04c038b6a3c82eb Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 12 Aug 2021 16:49:27 +0200 Subject: [PATCH 050/316] removed trace_on imports --- monitor.py | 1 - plugins/edsm.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/monitor.py b/monitor.py index 7ac856dd..dac75ae9 100644 --- a/monitor.py +++ b/monitor.py @@ -18,7 +18,6 @@ from typing import Tuple if TYPE_CHECKING: import tkinter -import config as conf_module # Necessary to see the same config.trace_on as elsewhere import util_ships from config import config from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised diff --git a/plugins/edsm.py b/plugins/edsm.py index 65e8012e..76ac203e 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -23,7 +23,7 @@ import killswitch import myNotebook as nb # noqa: N813 import plug from companion import CAPIData -from config import applongname, appversion, config, debug_senders, trace_on +from config import applongname, appversion, config, debug_senders from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel From e82abc8f03092c82b31ff055b83647865dd7cc66 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Thu, 12 Aug 2021 19:36:27 +0300 Subject: [PATCH 051/316] inara.py: move `conf_module` import to satisfy isort --- plugins/inara.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inara.py b/plugins/inara.py index 48515856..024791c1 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -13,6 +13,7 @@ from typing import Sequence, Union, cast import requests +import config as conf_module # Necessary to see the same config.trace_on as elsewhere import edmc_data import killswitch import myNotebook as nb # noqa: N813 @@ -22,7 +23,6 @@ from companion import CAPIData from config import applongname, appversion, config, debug_senders from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel -import config as conf_module # Necessary to see the same config.trace_on as elsewhere logger = get_main_logger() From e75eb6dd4b5c760b7470f8429d1b5d139bc857b3 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 12 Aug 2021 20:27:54 +0200 Subject: [PATCH 052/316] clarified docs, added `*` and `all` support to --trace-on --- EDMarketConnector.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 259fcce3..aa80e4c2 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -78,7 +78,7 @@ if __name__ == '__main__': # noqa: C901 parser.add_argument( "--trace-all", - help="Disable trace-on functionality", + help="Disable trace-on functionality (show any and all trace messages, regardless of trace-on gates)", action='store_true' ) @@ -107,7 +107,7 @@ if __name__ == '__main__': # noqa: C901 parser.add_argument( '--trace-on', - help='Mark the selected trace logging as active.', + help='Mark the selected trace logging as active. * or all will ensure that every possible trace log appears (in the same way as --trace-all)', action='append', ) @@ -133,7 +133,7 @@ if __name__ == '__main__': # noqa: C901 if args.trace: level_to_set = logging.TRACE # type: ignore # it exists - elif args.trace_all: + if args.trace_all or '*' in args.trace_on or 'all' in args.trace_on: level_to_set = logging.TRACE_ALL # type: ignore # it exists if level_to_set is not None: From 80da39d393388677f8a65ab2dd124c6bb1565032 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 11:36:38 +0100 Subject: [PATCH 053/316] TRACE: Tweak `--trace-on` help reference to */all arg --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index aa80e4c2..a89d8d27 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -107,7 +107,7 @@ if __name__ == '__main__': # noqa: C901 parser.add_argument( '--trace-on', - help='Mark the selected trace logging as active. * or all will ensure that every possible trace log appears (in the same way as --trace-all)', + help='Mark the selected trace logging as active. "*" or "all" is equivalent to --trace-all', action='append', ) From e10e8390a3eed1bc1413d40164e8e15b2671f085 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 11:37:24 +0100 Subject: [PATCH 054/316] TRACE: Move `--trace-on` arg to between --trace and --trace-all --- EDMarketConnector.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index a89d8d27..d00ed92f 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -76,6 +76,12 @@ if __name__ == '__main__': # noqa: C901 action='store_true', ) + parser.add_argument( + '--trace-on', + help='Mark the selected trace logging as active. "*" or "all" is equivalent to --trace-all', + action='append', + ) + parser.add_argument( "--trace-all", help="Disable trace-on functionality (show any and all trace messages, regardless of trace-on gates)", @@ -105,12 +111,6 @@ if __name__ == '__main__': # noqa: C901 action='append', ) - parser.add_argument( - '--trace-on', - help='Mark the selected trace logging as active. "*" or "all" is equivalent to --trace-all', - action='append', - ) - auth_options = parser.add_mutually_exclusive_group(required=False) auth_options.add_argument('--force-localserver-for-auth', help='Force EDMC to use a localhost webserver for Frontier Auth callback', From cf399da8d72c6a4b97fa9f596fa8e5d5a343c3e2 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 11:39:19 +0100 Subject: [PATCH 055/316] TRACE: Tweak `--trace-all` help message Yes, it actually uses TRACE_ALL level, that's why I didn't capitlise 'trace' in 'Force trace level logging ...'. --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index d00ed92f..5eadd34d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -84,7 +84,7 @@ if __name__ == '__main__': # noqa: C901 parser.add_argument( "--trace-all", - help="Disable trace-on functionality (show any and all trace messages, regardless of trace-on gates)", + help='Force trace level logging, with all possible --trace-on values active.', action='store_true' ) From 80976c8cf5970ae858dd245992e094b18b44e83f Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 11:55:10 +0100 Subject: [PATCH 056/316] TRACE: Don't require `--trace` with other trace options * `--trace-on ...` no longer requires `--trace` as well. * Corrected check for `--trace-on *|all`. * Updated Contributing.md to reflect mandated use of `logger.trace_if(...)`, with bare `logger.trace(...)` only for on the fly, code not going to be merged, use. --- Contributing.md | 30 ++++++++++++++---------------- EDMarketConnector.py | 4 ++-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Contributing.md b/Contributing.md index 54e39907..814ff7ca 100644 --- a/Contributing.md +++ b/Contributing.md @@ -408,28 +408,26 @@ In addition to that we utilise one of the user-defined levels as: command-line argument and `.bat` file for users to enable it. It cannot be selected from Settings in the UI. - As well as just using bare `logger.trace(...)` you can also gate it to only - log if asked to at invocation time by utilising the `--trace-on ...` - command-line argument. e.g. - `EDMarketConnector.py --trace --trace-on edsm-cmdr-events`. Note how you - still need to include `--trace`. + **Do not use a bare `logger.trace(...)` call** unless you're 100% certain + it's only temporary **and will be removed before any code merge**. In + that case you would utilise `EDMarketConnector.py --trace` to see the output. - `--trace-on` stores its arguments in `config.trace_on`. - To make use of `--trace-on`, you can either check `config.trace_on` yourself: - - ```python - import config - if 'my-trace-rule' in config.trace_on: - logger.trace('my log message') - ``` - - or you can use the helper method provided on `logger`: + Instead, you should gate any TRACE logging using the `trace_if()` helper + method provided on `logger`: ```python logger.trace_if('my-trace-rule', 'my-log-message') ``` + + This would then be triggered by running EDMarketConnector with the + appropriate command-line arguments: + + EDMarketConnector.py --trace-on my-trace-rule - This way you can set up TRACE logging that won't spam just because `--trace` is used. + Note that you do **not** also need to specify `--trace`, that's implied. + + This way you can set up TRACE logging that won't spam just because `--trace` + is used. --- diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 5eadd34d..a64fad7b 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -130,10 +130,10 @@ if __name__ == '__main__': # noqa: C901 args = parser.parse_args() level_to_set: Optional[int] = None - if args.trace: + if args.trace or args.trace_on: level_to_set = logging.TRACE # type: ignore # it exists - if args.trace_all or '*' in args.trace_on or 'all' in args.trace_on: + if args.trace_all or (args.trace_on and ('*' in args.trace_on or 'all' in args.trace_on)): level_to_set = logging.TRACE_ALL # type: ignore # it exists if level_to_set is not None: From f7d0db835424a8f1e0f730f15de5e7f2ead5e9c2 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 12:03:04 +0100 Subject: [PATCH 057/316] TRACE: Add some INFO logging if activating TRACE or TRACE_ALL --- EDMarketConnector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index a64fad7b..b2d4cba8 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -132,9 +132,11 @@ if __name__ == '__main__': # noqa: C901 level_to_set: Optional[int] = None if args.trace or args.trace_on: level_to_set = logging.TRACE # type: ignore # it exists + logger.info('Setting TRACE level debugging due to either --trace or a --trace-on') if args.trace_all or (args.trace_on and ('*' in args.trace_on or 'all' in args.trace_on)): level_to_set = logging.TRACE_ALL # type: ignore # it exists + logger.info('Setting TRACE_ALL level debugging due to either --trace-all or a --trace-on *|all') if level_to_set is not None: logger.setLevel(level_to_set) From 28f7a2b2fe0fb05da287561a09c7c9f8e2740d40 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Fri, 13 Aug 2021 14:07:50 +0300 Subject: [PATCH 058/316] PLUGINS.md: add ref link for systems to CQC maps mapping --- PLUGINS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLUGINS.md b/PLUGINS.md index cf13d176..74185aba 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -748,7 +748,7 @@ New in version 5.1.3+ def journal_entry_cqc(cmdr: str, is_beta: bool, entry: Dict[str, Any], state: Dict[str, Any]) -> None: if entry['event'] == 'Location': # We loaded to CQC match, lets detect map! - cqc_maps = { # dict to map systems names to CQC maps + cqc_maps = { # dict to map systems names to CQC maps, ref: https://forums.frontier.co.uk/threads/cqc-systems.234394/ 'Bleae Aewsy GA-Y d1-14': 'Asteria Point', 'Eta Cephei': 'Cluster Compound', 'Theta Ursae Majoris': 'Elevate', From 29df3869cd894e04d0c02edad58c69edcd45058d Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Fri, 13 Aug 2021 14:15:46 +0300 Subject: [PATCH 059/316] PLUGINS.md: update first EDMC version with `journal_entry_cqc` --- PLUGINS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLUGINS.md b/PLUGINS.md index 74185aba..378369bf 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -743,7 +743,7 @@ Examples of this are: --- ### Journal entry CQC -New in version 5.1.3+ +New in version 5.2.0 ```python def journal_entry_cqc(cmdr: str, is_beta: bool, entry: Dict[str, Any], state: Dict[str, Any]) -> None: if entry['event'] == 'Location': From c65ef0a4d6dd38df259f3c1387a08e3da282b134 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Fri, 13 Aug 2021 14:41:35 +0300 Subject: [PATCH 060/316] PLUGINS.md: deduplicate content table for `state` in `journal_entry_cqc` --- PLUGINS.md | 56 +----------------------------------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 378369bf..2a41b7ae 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -767,62 +767,8 @@ This gets called when EDMC sees a new entry in the game's journal and we are cur | `entry` | `Dict[str, Any]` | The journal event | | `state` | `Dict[str, Any]` | More info about the commander, their ship, and their cargo (see below) | -Content of `state` (updated to the current journal entry): +Content of `state` same as for [`journal_entry`](#journal-entry). -| Field | Type | Description | -| :------------------- | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------- | -| `GameLanguage` | `Optional[str]` | `language` value from `Fileheader` event. | -| `GameVersion` | `Optional[str]` | `version` value from `Fileheader` event. | -| `GameBuild` | `Optional[str]` | `build` value from `Fileheader` event. | -| `Captain` | `Optional[str]` | Name of the commander who's crew you're on, if any | -| `Cargo` | `Optional[dict]` | Current cargo. Note that this will be totals, and any mission specific duplicates will be counted together | -| `CargoJSON` | `Optional[dict]` | content of cargo.json as of last read. | -| `Credits` | `int` | Current credits balance | -| `FID` | `str` | Frontier commander ID | -| `Horizons` | `bool` | From `LoadGame` event. | -| `Odyssey` | `bool` | From `LoadGame` event. `False` if not present, else the event value. | -| `Loan` | `Optional[int]` | Current loan amount, if any | -| `Raw` | `Optional[dict]` | Current raw engineering materials | -| `Manufactured` | `Optional[dict]` | Current manufactured engineering materials | -| `Encoded` | `Optional[dict]` | Current encoded engineering materials | -| `Component` | `Optional[dict]` | Current component materials | -| `Engineers` | `Optional[dict]` | Current Raw engineering materials | -| `Rank` | `Optional[Dict[str, Tuple[int, int]]` | Current ranks, each entry is a tuple of the current rank, and age | -| `Statistics` | `Optional[dict]` | Contents of a Journal Statistics event, ie, data shown in the stats panel. See the Journal manual for more info | -| `Role` | `Optional[str]` | Current role if in multi-crew, one of `Idle`, `FireCon`, `FighterCon` | -| `Friends` | `set` | Currently online friends | -| `ShipID` | `Optional[int]` | Frontier ID of current ship | -| `ShipIdent` | `Optional[str]` | Current user-set ship ID | -| `ShipName` | `Optional[str]` | Current user-set ship name | -| `ShipType` | `Optional[str]` | Internal name for the current ship type | -| `HullValue` | `Optional[int]` | Current ship value, excluding modules | -| `ModulesValue` | `Optional[int]` | Value of the current ship's modules | -| `Rebuy` | `Optional[int]` | Current ship's rebuy cost | -| `Modules` | `Optional[dict]` | Currently fitted modules | -| `NavRoute` | `Optional[dict]` | Last plotted multi-hop route | -| `ModuleInfo` | `Optional[dict]` | Last loaded ModulesInfo.json data | -| `OnFoot` | `Optional[bool]` | Whether the Cmdr is on foot | -| `Component` | `Optional[dict]` | 'Component' MicroResources in Odyssey, `int` count each. | -| `Item` | `Optional[dict]` | 'Item' MicroResources in Odyssey, `int` count each. | -| `Consumable` | `Optional[dict]` | 'Consumable' MicroResources in Odyssey, `int` count each. | -| `Data` | `Optional[dict]` | 'Data' MicroResources in Odyssey, `int` count each. | -| `BackPack` | `Optional[dict]` | `dict` of Odyssey MicroResources in backpack. | -| `BackpackJSON` | `Optional[dict]` | Content of Backpack.json as of last read. | -| `ShipLockerJSON` | `Optional[dict]` | Content of ShipLocker.json as of last read. | -| `SuitCurrent` | `Optional[dict]` | CAPI-returned data of currently worn suit. NB: May be `None` if no data. | -| `Suits` | `Optional[dict]`[1] | CAPI-returned data of owned suits. NB: May be `None` if no data. | -| `SuitLoadoutCurrent` | `Optional[dict]` | CAPI-returned data of current Suit Loadout. NB: May be `None` if no data. | -| `SuitLoadouts` | `Optional[dict]`[1] | CAPI-returned data of all Suit Loadouts. NB: May be `None` if no data. | -| `Taxi` | `Optional[bool]` | Whether or not we're currently in a taxi. NB: This is best effort with what the journals provide. | -| `Dropship` | `Optional[bool]` | Whether or not the above taxi is a Dropship | -| `Body` | `Optional[str]` | The body we're currently on / in the SOI of | -| `BodyType` | `Optional[str]` | The type of body that `Body` refers to | - -[1] - 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. ___ ### Shutdown From 411bd0edf2e4df847d6a882aaaad81d50ef26857 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 13:19:46 +0100 Subject: [PATCH 061/316] journal/CQC: Minor documentation tweaks for language/grammar --- PLUGINS.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 2a41b7ae..d389339a 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -678,6 +678,8 @@ New in version 5.1.1: `state` now has a `ShipLockerJSON` member containing the un-changed, loaded, JSON from the `ShipLockerJSON.json` file. +___ + ##### Synthetic Events A special "StartUp" entry is sent if EDMC is started while the game is already @@ -703,6 +705,8 @@ between the two scenarios. This event is not sent when EDMC is running on a different machine so you should not *rely* on receiving this event. +--- + ##### Augmented Events In some cases we augment the events, as seen in the Journal, with extra data. @@ -740,9 +744,7 @@ Examples of this are: --- ---- - -### Journal entry CQC +### Journal entry in CQC New in version 5.2.0 ```python def journal_entry_cqc(cmdr: str, is_beta: bool, entry: Dict[str, Any], state: Dict[str, Any]) -> None: @@ -758,7 +760,8 @@ def journal_entry_cqc(cmdr: str, is_beta: bool, entry: Dict[str, Any], state: Di logger.info(f'Loaded to CQC map {cqc_map}') ``` -This gets called when EDMC sees a new entry in the game's journal and we are currently in CQC. +This is called for new journal entries, instead of `journal_entry()`, when the +player is in Arena (CQC). | Parameter | Type | Description | | :-------- | :--------------: | :--------------------------------------------------------------------- | @@ -767,9 +770,10 @@ This gets called when EDMC sees a new entry in the game's journal and we are cur | `entry` | `Dict[str, Any]` | The journal event | | `state` | `Dict[str, Any]` | More info about the commander, their ship, and their cargo (see below) | -Content of `state` same as for [`journal_entry`](#journal-entry). +The content of `state` will be the same as for [`journal_entry`](#journal-entry), +so check there for documentation. -___ +--- ### Shutdown From 6c34ceaba9c8c6b03b77bb8e794e6a5e1043e407 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 13:32:36 +0100 Subject: [PATCH 062/316] PLUGINS.md: s/EDMC/EDMarketConnector/ almost everywhere & reflow The reflow is on *all* non-code/table text to be within 80 columns, excepting if a link just makes that impossible. --- PLUGINS.md | 207 ++++++++++++++++++++++++++++------------------------- 1 file changed, 110 insertions(+), 97 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index d389339a..c0afa177 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -1,6 +1,6 @@ # Developer Plugin Documentation -Plugins allow you to customise and extend the behavior of EDMC. +Plugins allow you to customise and extend the behavior of EDMarketConnector. ## Installing a Plugin @@ -12,28 +12,31 @@ wiki. Check [Releasing.md](docs/Releasing.md#environment) to be sure of the current version of Python that we've tested against. -Plugins are loaded when EDMC starts up. +Plugins are loaded when EDMarketConnector starts up. Each plugin has it's own folder in the `plugins` directory: - Windows: `%LOCALAPPDATA%\EDMarketConnector\plugins` - Mac: `~/Library/Application Support/EDMarketConnector/plugins` -- Linux: `$XDG_DATA_HOME/EDMarketConnector/plugins`, or `~/.local/share/EDMarketConnector/plugins` if `$XDG_DATA_HOME` is unset. +- Linux: `$XDG_DATA_HOME/EDMarketConnector/plugins`, or + `~/.local/share/EDMarketConnector/plugins` if `$XDG_DATA_HOME` is unset. Plugins are python files. The plugin folder must have a file named `load.py` that must provide one module level function and optionally provide a few others. -If you're running from source (which allows for debugging with e.g. [PyCharm](https://www.jetbrains.com/pycharm/features/)) +If you're running from source (which allows for debugging with e.g. +[PyCharm](https://www.jetbrains.com/pycharm/features/)) 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). +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 into the .exe via the py2exe build process). Please be sure to read the [Avoiding potential pitfalls](#avoiding-potential-pitfalls) -section, else you might inadvertently cause issues for the core EDMC code -including whole application crashes. +section, else you might inadvertently cause issues for the core +EDMarketConnector code including whole application crashes. It is highly advisable to ensure you are aware of all EDMarketConnector releases, including the pre-releases. The -beta and -rc changelogs will @@ -118,9 +121,9 @@ the packaged executeable all output is redirected to a log file. See [Reporting a problem](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#reporting-a-problem) for the location of this log file. -EDMC now implements proper logging using the Python `logging` module. Plugin -developers should now use the following code instead of simple `print(...)` -statements. +EDMarketConnector now implements proper logging using the Python `logging` +module. Plugin developers should now use the following code instead of simple +`print(...)` statements. Insert this at the top-level of your load.py file (so not inside `plugin_start3()`): @@ -160,13 +163,14 @@ exactly the folder name. Our custom `qualname` and `class` formatters won't work with a 'bare' logger, and will cause your code to throw exceptions if you're not using our supplied logger. -If running with 4.1.0-beta1 or later of EDMC the logging setup happens in -the core code and will include the extra logfile destinations. If your -plugin is run under a pre-4.1.0 version of EDMC then the above will set up -basic logging only to the console (and thus redirected to the log file). +If running with 4.1.0-beta1 or later of EDMarketConnector the logging setup +happens in the core code and will include the extra logfile destinations. If +your plugin is run under a pre-4.1.0 version of EDMarketConnector then the +above will set up basic logging only to the console (and thus redirected to +the log file). -If you're certain your plugin will only be run under EDMC 4.1.0 or newer then -you can remove the `if` clause. +If you're certain your plugin will only be run under EDMarketConnector 4.1.0 +or newer then you can remove the `if` clause. Replace all `print(...)` statements with one of the following: @@ -226,26 +230,26 @@ from config import appversion # Yes, just blow up if config.appverison is neither str or callable - logger.info(f'Core EDMC version: {core_version}') + logger.info(f'Core EDMarketConnector version: {core_version}') # And then compare like this if core_version < semantic_version.Version('5.0.0-beta1'): - logger.info('EDMC core version is before 5.0.0-beta1') + logger.info('EDMarketConnector core version is before 5.0.0-beta1') else: - logger.info('EDMC core version is at least 5.0.0-beta1') + logger.info('EDMarketConnector core version is at least 5.0.0-beta1') ``` --- ## Startup -EDMC will import the `load.py` file as a module and then call the +EDMarketConnector will import the `load.py` file as a module and then call the `plugin_start3()` function. ```python def plugin_start3(plugin_dir: str) -> str: """ - Load this plugin into EDMC + Load this plugin into EDMarketConnector """ print(f"I am loaded! My plugin folder is {plugin_dir}") return "Test" @@ -267,8 +271,8 @@ Mac, and `$TMP/EDMarketConnector.log` on Linux. ## Avoiding potential pitfalls There are a number of things that your code should either do or avoiding -doing so as to play nicely with the core EDMC code and not risk causing -application crashes or hangs. +doing so as to play nicely with the core EDMarketConnector code and not risk +causing application crashes or hangs. ### Be careful about the name of your plugin directory @@ -344,15 +348,17 @@ So instead use: ### Configuration If you want your plugin to be configurable via the GUI you can define a frame -(panel) to be displayed on its own tab in EDMC's settings dialog. The tab -title will be the value that you returned from `plugin_start3`. Use widgets -from EDMC's myNotebook.py for the correct look-and-feel. You can be notified -when the settings dialog is closed so you can save your settings. +(panel) to be displayed on its own tab in EDMarketConnector's settings dialog. +The tab title will be the value that you returned from `plugin_start3`. Use +widgets from EDMarketConnector's myNotebook.py for the correct look-and-feel. +You can be notified when the settings dialog is closed, so you can save your +settings. You can use `set()` and `get_$type()` (where type is one of: `int`, `bool`, -`str`, `list`) from EDMC's `config.config` object to retrieve 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. +`str`, `list`) from EDMarketConnector's `config.config` object to retrieve +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 @@ -377,12 +383,12 @@ if not hasattr(config, 'get_list'): ``` **Be sure to use a unique prefix for any settings you save so as not to clash -with core EDMC or other plugins.** +with core EDMarketConnector or other plugins.** -Use `number_from_string()` from EDMC's `l10n.Locale` object to parse input -numbers in a locale-independent way. NB: the old CamelCase versions of -`number_from_string` and `string_from_number` do still exist, but are -deprecated. They will continue to work, but will throw warnings. +Use `number_from_string()` from EDMarketConnector's `l10n.Locale` object to +parse input numbers in a locale-independent way. NB: the old CamelCase +versions of `number_from_string` and `string_from_number` do still exist, but +are deprecated. They will continue to work, but will throw warnings. Note that in the following example the function signature defines that it returns `Optional[tk.Frame]` only because we need to allow for `None` if @@ -401,7 +407,7 @@ my_setting: Optional[tk.IntVar] = None def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> Optional[tk.Frame]: """ - Return a TK Frame for adding to the EDMC settings dialog. + Return a TK Frame for adding to the EDMarketConnector settings dialog. """ global my_setting my_setting = tk.IntVar(value=config.get_int("MyPluginSetting")) # Retrieve saved value from config @@ -438,15 +444,15 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None: ### Display -You can also have your plugin add an item to the EDMC main window and update -from your event hooks. This works in the same way as `plugin_prefs()`. For a -simple one-line item return a `tk.Label` widget or a 2 tuple of widgets. -For a more complicated item create a tk.Frame widget and populate it with other -ttk widgets. Return `None` if you just want to use this as a callback after the -main window and all other plugins are initialised. +You can also have your plugin add an item to the EDMarketConnector main window +and update from your event hooks. This works in the same way as +`plugin_prefs()`. For a simple one-line item return a `tk.Label` widget or a 2 +tuple of widgets. For a more complicated item create a tk.Frame widget and +populate it with other ttk widgets. Return `None` if you just want to use this +as a callback after the main window and all other plugins are initialised. -You can use `string_from_number()` from EDMC's `l10n.Locale` object to format -numbers in your widgets in a locale-independent way. +You can use `string_from_number()` from EDMarketConnector's `l10n.Locale` +object to format numbers in your widgets in a locale-independent way. ```python from typing import Optional, Tuple @@ -457,7 +463,7 @@ status: Optional[tk.Label] def plugin_app(parent: tk.Frame) -> Tuple[tk.Label, tk.Label]: """ - Create a pair of TK widgets for the EDMC main window + Create a pair of TK widgets for the EDMarketConnector main window """ global status label = tk.Label(parent, text="Status:") # By default widgets inherit the current theme's colors @@ -473,7 +479,7 @@ def some_other_function() -> None: | Parameter | Type | Description | | :-------- | :---------------------------------------------: | :---------------------------------------------------------- | -| `parent` | `tk.Frame` | The root EDMC window | +| `parent` | `tk.Frame` | The root EDMarketConnector window | | `RETURN` | `Union[tk.Widget, Tuple[tk.Widget, tk.Widget]]` | A widget to add to the main window. See below for more info | The return from `plugin_app()` can either be any widget (`Frame`, `Label`, @@ -496,7 +502,7 @@ frame: Optional[tk.Frame] = None def plugin_app(parent: tk.Frame) -> tk.Frame: """ - Create a frame for the EDMC main window + Create a frame for the EDMarketConnector main window """ global frame frame = tk.Frame(parent) @@ -519,9 +525,10 @@ See [Avoiding potential pitfalls](#avoiding-potential-pitfalls). ### Events -Once you have created your plugin and EDMC has loaded it there are four other -functions you can define to be notified by EDMC when something happens: -`journal_entry()`, `journal_entry_cqc()`, `dashboard_entry()` and `cmdr_data()`. +Once you have created your plugin and EDMarketConnector has loaded it there +are four other functions you can define to be notified by EDMarketConnector +when something happens: `journal_entry()`, `journal_entry_cqc()`, +`dashboard_entry()` and `cmdr_data()`. Your events all get called on the main Tkinter loop so be sure not to block for very long or the app will appear to freeze. If you have a long running @@ -551,7 +558,7 @@ def journal_entry( logger.info(f'Arrived at {entry["StarSystem"]}') ``` -This gets called when EDMC sees a new entry in the game's journal. +This gets called when EDMarketConnector sees a new entry in the game's journal. | Parameter | Type | Description | | :-------- | :--------------: | :--------------------------------------------------------------------- | @@ -682,10 +689,10 @@ ___ ##### Synthetic Events -A special "StartUp" entry is sent if EDMC is started while the game is already -running. In this case you won't receive initial events such as "LoadGame", -"Rank", "Location", etc. However the `state` dictionary will reflect the -cumulative effect of these missed events. +A special "StartUp" entry is sent if EDMarketConnector is started while the +game is already running. In this case you won't receive initial events such as +"LoadGame", "Rank", "Location", etc. However the `state` dictionary will +reflect the cumulative effect of these missed events. **NB: Any of the values in this might be `None` if the Cmdr has loaded into Arena (CQC) from the Main Menu.** @@ -702,7 +709,7 @@ 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).** -This event is not sent when EDMC is running on a different +This event is not sent when EDMarketConnector is running on a different machine so you should not *rely* on receiving this event. --- @@ -782,7 +789,7 @@ This gets called when the user closes the program: ```python def plugin_stop() -> None: """ - EDMC is closing + EDMarketConnector is closing """ print("Farewell cruel world!") ``` @@ -926,23 +933,23 @@ time after the corresponding `journal_entry()` event. ## Error messages -You can display an error in EDMC's status area by returning a string from your -`journal_entry()`, `dashboard_entry()` or `cmdr_data()` function, or -asynchronously (e.g. from a "worker" thread that is performing a long running -operation) by calling `plug.show_error()`. Either method will cause the "bad" -sound to be played (unless the user has muted sound). +You can display an error in EDMarketConnector's status area by returning a +string from your `journal_entry()`, `dashboard_entry()` or `cmdr_data()` +function, or asynchronously (e.g. from a "worker" thread that is performing a +long-running operation) by calling `plug.show_error()`. Either method will +cause the "bad" sound to be played (unless the user has muted sound). -The status area is shared between EDMC itself and all other plugins, so your -message won't be displayed for very long. Create a dedicated widget if you need -to display routine status information. +The status area is shared between EDMarketConnector itself and all other +plugins, so your message won't be displayed for very long. Create a dedicated +widget if you need to display routine status information. --- ## Localisation -You can localise your plugin to one of the languages that EDMC itself supports. -Add the following boilerplate near the top of each source file that contains -strings that needs translating: +You can localise your plugin to one of the languages that EDMarketConnector +itself supports. Add the following boilerplate near the top of each source +file that contains strings that needs translating: ```python import l10n @@ -956,9 +963,9 @@ Wrap each string that needs translating with the `_()` function, e.g.: status["text"] = _('Happy!') # Main window status ``` -If you display localized strings in EDMC's main window you should refresh them -in your `prefs_changed` function in case the user has changed their preferred -language. +If you display localized strings in EDMarketConnector's main window you should +refresh them in your `prefs_changed` function in case the user has changed +their preferred language. Translation files should reside in folder named `L10n` inside your plugin's folder. Files must be in macOS/iOS ".strings" format, encoded as UTF-8. You can @@ -967,7 +974,7 @@ in your plugin's folder. This extracts all the translatable strings from Python files in your plugin's folder and places them in a file named `en.template` in the `L10n` folder. Rename this file as `<language_code>.strings` and edit it. -See EDMC's own [`L10n`](https://github.com/EDCD/EDMarketConnector/tree/main/L10n) +See EDMarketConnector's own [`L10n`](https://github.com/EDCD/EDMarketConnector/tree/main/L10n) folder for the list of supported language codes and for example translation files. @@ -976,9 +983,9 @@ files. ## Python Package Plugins A _Package Plugin_ is both a standard Python package (i.e. contains an -`__init__.py` file) and an EDMC plugin (i.e. contains a `load.py` file -providing at minimum a `plugin_start3()` function). These plugins are loaded -before any non-Package plugins. +`__init__.py` file) and an EDMarketConnector plugin (i.e. contains a `load.py` +file providing at minimum a `plugin_start3()` function). These plugins are +loaded before any non-Package plugins. Other plugins can access features in a Package Plugin by `import`ing the package by name in the usual way. @@ -1085,16 +1092,20 @@ step for the extra module(s) until it works. You can debug your http post requests using the builtin debug webserver. -To add support for said debug webserver to your plugin, you need to check `config.debug_senders` (`list[str]`) for -some indicator string for your plugin. `debug_senders` is generated from args to `--debug-sender` on the invocation -command line. +To add support for said debug webserver to your plugin, you need to check +`config.debug_senders` (`list[str]`) for some indicator string for your +plugin. `debug_senders` is generated from args to `--debug-sender` on the +invocation command line. If said string exists, `DEBUG_WEBSERVER_HOST` and `DEBUG_WEBSERVER_PORT` in -`edmc_data` will contain the host and port for the currently running local webserver. Simply redirect your requests -there, and your requests will be logged to disk. For organisation, rewrite your request path to simply be `/pluginname`. +`edmc_data` will contain the host and port for the currently running local +webserver. Simply redirect your requests there, and your requests will be +logged to disk. For organisation, rewrite your request path to simply be +`/pluginname`. -Logs exist in `$TEMP/EDMarketConnector/http_debug/$path.log`. If somehow you manage to cause a directory traversal, your -data will not be saved to disk at all. You will see this in EDMCs log. +Logs exist in `$TEMP/EDMarketConnector/http_debug/$path.log`. If somehow you +manage to cause a directory traversal, your data will not be saved to disk at +all. You will see this in EDMarketConnectors log. The simplest way to go about adding support is: @@ -1110,15 +1121,16 @@ if 'my_plugin' in debug_senders: ``` -For returned data, you can modify `debug_webserver.DEFAULT_RESPONSES` (`dict[str, Union[Callable[[str], str]], str])` -with either a function that accepts a single string (the raw post data) and returns a single string -(the response to send), or with a string if your required response is simple. +For returned data, you can modify `debug_webserver.DEFAULT_RESPONSES` +(`dict[str, Union[Callable[[str], str]], str])` with either a function that +accepts a single string (the raw post data) and returns a single string (the +response to send), or with a string if your required response is simple. ## Disable a plugin -EDMC now lets you disable a plugin without deleting it, simply rename the -plugin folder to append ".disabled". Eg, -"SuperSpaceHelper" -> "SuperSpaceHelper.disabled" +EDMarketConnector now lets you disable a plugin without deleting it, simply +rename the plugin folder to append ".disabled". Eg, "SuperSpaceHelper" -> +"SuperSpaceHelper.disabled" Disabled and enabled plugins are listed on the "Plugins" Settings tab @@ -1126,18 +1138,19 @@ Disabled and enabled plugins are listed on the "Plugins" Settings tab ## Migration from Python 2.7 -Starting with pre-release 3.5 EDMC used Python 3.7. The first full -release under Python 3.7 was 4.0.0.0. The 4.2.x series was the last to use -Python 3.7, with releases moving on to the latest Python 3.9.x after that. +Starting with pre-release 3.5 EDMarketConnector used Python 3.7. The first +full release under Python 3.7 was 4.0.0.0. The 4.2.x series was the last to +use Python 3.7, with releases moving on to the latest Python 3.9.x after that. This is a brief outline of the steps required to migrate a plugin from earlier -versions of EDMC: +versions of EDMarketConnector: - Rename the function `plugin_start` to `plugin_start3(plugin_dir)`. - Plugins without a `plugin_start3` function are listed as disabled on EDMC's - "Plugins" tab and a message like "plugin SuperSpaceHelper needs migrating" - appears in the log. Such plugins are also listed in a section "Plugins - Without Python 3.x Support:" on the Settings > Plugins tab. + Plugins without a `plugin_start3` function are listed as disabled on + EDMarketConnector's "Plugins" tab and a message like "plugin + SuperSpaceHelper needs migrating" appears in the log. Such plugins are + also listed in a section "Plugins Without Python 3.x Support:" on the + `Settings` > `Plugins` tab. - Check that callback functions `plugin_prefs`, `prefs_changed`, `journal_entry`, `dashboard_entry` and `cmdr_data`, if used, are declared @@ -1150,5 +1163,5 @@ versions of EDMC: 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 +versions of this application, and you shouldn't support use of them with your plugin. From f977ad6716da0c3297aa633e61a511ef374a6ddc Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 13:50:46 +0100 Subject: [PATCH 063/316] PLUGINS.md: General PyCharm alerts pass A few bits of example code tweaked to be more correct, but to make PyCharm truly happy about the file we'd need to expand the examples to stupid proportions (extra imports, indent changes etc). --- PLUGINS.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index c0afa177..e8a27474 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -130,6 +130,7 @@ Insert this at the top-level of your load.py file (so not inside ```python import logging +import os from config import appname @@ -323,9 +324,9 @@ should treat it as a variable. **Do NOT use**: ```python - from config import shutting_down + from config import config - if shutting_down(): + if config.shutting_down: # During shutdown ``` @@ -398,7 +399,6 @@ as in the code below. ```python import tkinter as tk -from tkinter import ttk import myNotebook as nb from config import config from typing import Optional @@ -468,7 +468,7 @@ def plugin_app(parent: tk.Frame) -> Tuple[tk.Label, tk.Label]: global status label = tk.Label(parent, text="Status:") # By default widgets inherit the current theme's colors status = tk.Label(parent, text="", foreground="yellow") # Override theme's foreground color - return (label, status) + return label, status # later on your event functions can update the contents of these widgets def some_other_function() -> None: @@ -493,7 +493,7 @@ You can dynamically add and remove widgets on the main window by returning a of that frame. ```python -from typing import Option +from typing import Optional import tkinter as tk from theme import theme @@ -803,8 +803,6 @@ before returning from this function. ### Player Dashboard ```python -import plug - def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]): is_deployed = entry['Flags'] & edmc_data.FlagsHardpointsDeployed sys.stderr.write("Hardpoints {}\n".format(is_deployed and "deployed" or "stowed")) @@ -916,9 +914,7 @@ def inara_notify_ship(event_data): `event_data` holds the response to an addCommanderShip or setCommanderShip event https://inara.cz/inara-api-docs/#event-11 """ if event_data.get('shipInaraID'): - logger.info( - f'Now in Inara ship {event_data['shipInaraID'],} at {event_data['shipInaraURL']} - ) + logger.info(f'Now in Inara ship {event_data["shipInaraID"],} at {event_data["shipInaraURL"]}') ``` If the player has chosen to "Send flight log and Cmdr status to Inara" this From 504e7c7eb5432b4592b2a08d157369d814ccd956 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 13 Aug 2021 15:50:36 +0200 Subject: [PATCH 064/316] use glob matching for trace-on matching --- EDMCLogging.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index c6928a63..19dc60f6 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -47,6 +47,7 @@ from sys import _getframe as getframe from threading import get_native_id as thread_native_id from traceback import print_exc from typing import TYPE_CHECKING, Tuple, cast +from fnmatch import fnmatch import config as config_mod from config import appcmdname, appname, config @@ -92,12 +93,11 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None: - if condition not in config_mod.trace_on: - self._log(logging.TRACE_ALL, message, args, **kwargs) # type: ignore # we added it - - else: - # its in there, it gets to end up in regular TRACE + if any(fnmatch(condition, p) for p in config_mod.trace_on): self._log(logging.TRACE, message, args, **kwargs) # type: ignore # we added it + return + + self._log(logging.TRACE_ALL, message, args, **kwargs) # type: ignore # we added it logging.Logger.trace_if = _trace_if # type: ignore @@ -114,15 +114,16 @@ if TYPE_CHECKING: """LoggerMixin is a fake class that tells type checkers that trace exists on a given type.""" def trace(self, message, *args, **kwargs) -> None: - """Fake trace method.""" - return self._log(LEVEL_TRACE, message, args, **kwargs) + """See implementation above.""" + ... def trace_if(self, condition: str, message, *args, **kwargs) -> None: - """Fake trace if method, traces only if condition exists in trace_on.""" - if condition in config_mod.trace_on: - return self._log(LEVEL_TRACE, message, *args, **kwargs) - else: - return self._log(LEVEL_TRACE_ALL, message, *args, **kwargs) + """ + Fake trace if method, traces only if condition exists in trace_on. + + See implementation above. + """ + ... class Logger: From 647364c1ee45b7d19efc5ae97faff4487cf80bbc Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:15:28 +0100 Subject: [PATCH 065/316] EDMC: Add --trace-on, --trace-all & use monitor.currentdir In testing `EDMC.py --trace` I ran into issues with NavRoute and ShipLocker events wanting to open the auxilary files, but it failing. This turned out to be because that code assumes `monitor.currentdir` is set and EDMC.py was using a local variable rather than setting and using it. --- EDMC.py | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/EDMC.py b/EDMC.py index b3e6d0c9..4d7a5dd8 100755 --- a/EDMC.py +++ b/EDMC.py @@ -123,10 +123,25 @@ def main(): # noqa: C901, CCR001 help='Set the logging loglevel to one of: ' 'CRITICAL, ERROR, WARNING, INFO, DEBUG, TRACE', ) - group_loglevel.add_argument('--trace', - help='Set the Debug logging loglevel to TRACE', - action='store_true', - ) + + parser.add_argument( + '--trace', + help='Set the Debug logging loglevel to TRACE', + action='store_true', + ) + + parser.add_argument( + '--trace-on', + help='Mark the selected trace logging as active. "*" or "all" is equivalent to --trace-all', + action='append', + ) + + parser.add_argument( + "--trace-all", + help='Force trace level logging, with all possible --trace-on values active.', + action='store_true' + ) + parser.add_argument('-a', metavar='FILE', help='write ship loadout to FILE in Companion API json format') parser.add_argument('-e', metavar='FILE', help='write ship loadout to FILE in E:D Shipyard plain text format') parser.add_argument('-l', metavar='FILE', help='write ship locations to FILE in CSV format') @@ -150,8 +165,18 @@ def main(): # noqa: C901, CCR001 return - if args.trace: - edmclogger.set_channels_loglevel(logging.TRACE) + level_to_set: Optional[int] = None + if args.trace or args.trace_on: + level_to_set = logging.TRACE # type: ignore # it exists + logger.info('Setting TRACE level debugging due to either --trace or a --trace-on') + + if args.trace_all or (args.trace_on and ('*' in args.trace_on or 'all' in args.trace_on)): + level_to_set = logging.TRACE_ALL # type: ignore # it exists + logger.info('Setting TRACE_ALL level debugging due to either --trace-all or a --trace-on *|all') + + if level_to_set is not None: + logger.setLevel(level_to_set) + edmclogger.set_channels_loglevel(level_to_set) elif args.loglevel: if args.loglevel not in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'TRACE'): @@ -188,15 +213,15 @@ sys.path: {sys.path}''' # Get state from latest Journal file logger.debug('Getting state from latest journal file') try: - logdir = config.get_str('journaldir', default=config.default_journal_dir) - if not logdir: - logdir = config.default_journal_dir + monitor.currentdir = config.get_str('journaldir', default=config.default_journal_dir) + if not monitor.currentdir: + monitor.currentdir = config.default_journal_dir - logger.debug(f'logdir = "{logdir}"') - logfiles = sorted((x for x in os.listdir(logdir) if JOURNAL_RE.search(x)), + logger.debug(f'logdir = "{monitor.currentdir}"') + logfiles = sorted((x for x in os.listdir(monitor.currentdir) if JOURNAL_RE.search(x)), key=lambda x: x.split('.')[1:]) - logfile = join(logdir, logfiles[-1]) + logfile = join(monitor.currentdir, logfiles[-1]) logger.debug(f'Using logfile "{logfile}"') with open(logfile, 'r', encoding='utf-8') as loghandle: From df84b8aa643e62dad7a8aa15f10259daef8dfd88 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:19:54 +0100 Subject: [PATCH 066/316] companion: Change logger.trace to logger.trace_if This adds only 'capi-query' to --trace-on possibilities. --- companion.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/companion.py b/companion.py index f2f571b6..eb4d8584 100644 --- a/companion.py +++ b/companion.py @@ -554,7 +554,7 @@ class Session(object): def query(self, endpoint: str) -> CAPIData: # noqa: CCR001 """Perform a query against the specified CAPI endpoint.""" - logger.trace(f'Performing query for endpoint "{endpoint}"') + logger.trace_if('capi-query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: if self.login(): return self.query(endpoint) @@ -564,7 +564,7 @@ class Session(object): raise CredentialsError('cannot make a query when unauthorized') try: - logger.trace('Trying...') + logger.trace_if('capi-query', 'Trying...') r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore except requests.ConnectionError as e: @@ -620,7 +620,6 @@ class Session(object): self.retrying = False if 'timestamp' not in data: - # logger.trace('timestamp not in data, adding from response headers') data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) # type: ignore # Update Odyssey Suit data From bb1faa67d41236cf511c19a88553ad7b4333fb3c Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:22:14 +0100 Subject: [PATCH 067/316] journal_lock: Convert to logger.trace_if(...) --- journal_lock.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/journal_lock.py b/journal_lock.py index d038709d..6b2e9951 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -56,7 +56,7 @@ class JournalLock: def open_journal_dir_lockfile(self) -> bool: """Open journal_dir lockfile ready for locking.""" self.journal_dir_lockfile_name = self.journal_dir_path / 'edmc-journal-lock.txt' # type: ignore - logger.trace(f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}') + logger.trace_if('journal-lock', f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}') try: self.journal_dir_lockfile = open(self.journal_dir_lockfile_name, mode='w+', encoding='utf-8') @@ -95,7 +95,7 @@ class JournalLock: :return: LockResult - See the class Enum definition """ if platform == 'win32': - logger.trace('win32, using msvcrt') + logger.trace_if('journal-lock', 'win32, using msvcrt') # win32 doesn't have fcntl, so we have to use msvcrt import msvcrt @@ -108,7 +108,7 @@ class JournalLock: return JournalLockResult.ALREADY_LOCKED else: # pytest coverage only sees this on !win32 - logger.trace('NOT win32, using fcntl') + logger.trace_if('journal-lock', 'NOT win32, using fcntl') try: import fcntl @@ -128,7 +128,7 @@ class JournalLock: self.journal_dir_lockfile.write(f"Path: {self.journal_dir}\nPID: {os_getpid()}\n") self.journal_dir_lockfile.flush() - logger.trace('Done') + logger.trace_if('journal-lock', 'Done') self.locked = True return JournalLockResult.LOCKED @@ -144,7 +144,7 @@ class JournalLock: unlocked = False if platform == 'win32': - logger.trace('win32, using msvcrt') + logger.trace_if('journal-lock', 'win32, using msvcrt') # win32 doesn't have fcntl, so we have to use msvcrt import msvcrt @@ -161,7 +161,7 @@ class JournalLock: unlocked = True else: # pytest coverage only sees this on !win32 - logger.trace('NOT win32, using fcntl') + logger.trace_if('journal-lock', 'NOT win32, using fcntl') try: import fcntl @@ -235,19 +235,19 @@ class JournalLock: def retry(self) -> None: """Handle user electing to Retry obtaining the lock.""" - logger.trace('User selected: Retry') + logger.trace_if('journal-lock_if', 'User selected: Retry') self.destroy() self.callback(True, self.parent) def ignore(self) -> None: """Handle user electing to Ignore failure to obtain the lock.""" - logger.trace('User selected: Ignore') + logger.trace_if('journal-lock', 'User selected: Ignore') self.destroy() self.callback(False, self.parent) def _destroy(self) -> None: """Destroy the Retry/Ignore popup.""" - logger.trace('User force-closed popup, treating as Ignore') + logger.trace_if('journal-lock', 'User force-closed popup, treating as Ignore') self.ignore() def update_lock(self, parent: tk.Tk) -> None: @@ -277,7 +277,7 @@ class JournalLock: :param retry: - does the user want to retry? Comes from the dialogue choice. :param parent: - The parent tkinter window. """ - logger.trace(f'We should retry: {retry}') + logger.trace_if('journal-lock', f'We should retry: {retry}') if not retry: return From 1378f765cc1a2b18f2bad85fc0b37cfe822aa652 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:30:58 +0100 Subject: [PATCH 068/316] monitor: Convert to logger.trace_if() --- monitor.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/monitor.py b/monitor.py index dac75ae9..31c2a507 100644 --- a/monitor.py +++ b/monitor.py @@ -336,8 +336,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below for line in loghandle: try: - # if b'"event":"Location"' in line: - # logger.trace('"Location" event in the past at startup') + if b'"event":"Location"' in line: + logger.trace_if('journal.locations', '"Location" event in the past at startup') self.parse_entry(line) # Some events are of interest even in the past @@ -417,22 +417,22 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if b'"event":"Continue"' in line: for _ in range(10): - logger.trace("****") - logger.trace('Found a Continue event, its being added to the list, we will finish this file up' - ' and then continue with the next') + logger.trace_if('journal.continuation', "****") + logger.trace_if('journal.continuation', 'Found a Continue event, its being added to the list, ' + 'we will finish this file up and then continue with the next') self.event_queue.put(line) if not self.event_queue.empty(): if not config.shutting_down: - # logger.trace('Sending <<JournalEvent>>') + logger.trace_if('journal.queue', 'Sending <<JournalEvent>>') self.root.event_generate('<<JournalEvent>>', when="tail") log_pos = loghandle.tell() if logfile != newlogfile: for _ in range(10): - logger.trace("****") + logger.trace_if('journal.file', "****") logger.info(f'New Journal File. Was "{logfile}", now "{newlogfile}"') logfile = newlogfile if loghandle: @@ -464,7 +464,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below ) if not config.shutting_down: - # logger.trace('Sending <<JournalEvent>>') + logger.trace_if('journal.queue', 'Sending <<JournalEvent>>') self.root.event_generate('<<JournalEvent>>', when="tail") self.game_was_running = False @@ -530,7 +530,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.cmdr = entry['Commander'] # 'Open', 'Solo', 'Group', or None for CQC (and Training - but no LoadGame event) if not entry.get('Ship') and not entry.get('GameMode') or entry.get('GameMode', '').lower() == 'cqc': - logger.trace_if('cqc-loadgame-events', f'loadgame to cqc: {entry}') + logger.trace_if('journal.loadgame.cqc', f'loadgame to cqc: {entry}') self.mode = 'CQC' else: @@ -764,8 +764,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['Body'] = entry.get('Body') self.state['BodyType'] = entry.get('BodyType') - # if event_type == 'location': - # logger.trace('"Location" event') + if event_type == 'location': + logger.trace_if('journal.locations', '"Location" event') elif event_type == 'fsdjump': self.planet = None @@ -1869,16 +1869,16 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below logger.debug('Called whilst self.thread is None, returning') return None - # logger.trace('Begin') + logger.trace_if('journal.queue', 'Begin') if self.event_queue.empty() and self.game_running(): logger.error('event_queue is empty whilst game_running, this should not happen, returning') return None - # logger.trace('event_queue NOT empty') + logger.trace_if('journal.queue', 'event_queue NOT empty') entry = self.parse_entry(self.event_queue.get_nowait()) - # if entry['event'] == 'Location': - # logger.trace('"Location" event') + if entry['event'] == 'Location': + logger.trace_if('journal.locations', '"Location" event') if not self.live and entry['event'] not in (None, 'Fileheader'): # Game not running locally, but Journal has been updated @@ -1906,8 +1906,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below ('SystemAddress', self.systemaddress), ]) - # if entry['event'] == 'Location': - # logger.trace('Appending "Location" event to event_queue') + if entry['event'] == 'Location': + logger.trace_if('journal.locations', 'Appending "Location" event to event_queue') self.event_queue.put(json.dumps(entry, separators=(', ', ':'))) From c8081f339ab10adfa5b618d1c8fb5359717659bb Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:45:11 +0100 Subject: [PATCH 069/316] plug.py: Use `logger.trace_if(...)` --- plug.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plug.py b/plug.py index 2899c237..2f3ab888 100644 --- a/plug.py +++ b/plug.py @@ -257,8 +257,8 @@ def notify_journal_entry(cmdr, is_beta, system, station, entry, state): :param is_beta: whether the player is in a Beta universe. :returns: Error message from the first plugin that returns one (if any) """ - # if entry['event'] in ('Location'): - # logger.trace('Notifying plugins of "Location" event') + if entry['event'] in ('Location'): + logger.trace_if('journal.locations', 'Notifying plugins of "Location" event') error = None for plugin in PLUGINS: From 305f8ca6b0521708543e03607ef5744ea4f013d6 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:48:09 +0100 Subject: [PATCH 070/316] protocol.py: Use `logger.trace_if(...)` --- protocol.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol.py b/protocol.py index 2a0e229e..ce5ea042 100644 --- a/protocol.py +++ b/protocol.py @@ -48,7 +48,7 @@ class GenericProtocolHandler: """Generate an auth event.""" self.lastpayload = url - logger.trace(f'Payload: {self.lastpayload}') + logger.trace_if('frontier-auth', f'Payload: {self.lastpayload}') if not config.shutting_down: logger.debug('event_generate("<<CompanionAuthEvent>>")') self.master.event_generate('<<CompanionAuthEvent>>', when="tail") @@ -298,7 +298,7 @@ elif (config.auth_force_edmc_protocol msg = MSG() # Calls GetMessageW: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessagew while GetMessageW(byref(msg), None, 0, 0) != 0: - logger.trace(f'DDE message of type: {msg.message}') + logger.trace_if('frontier-auth.windows', f'DDE message of type: {msg.message}') if msg.message == WM_DDE_EXECUTE: # GlobalLock does some sort of "please dont move this?" # https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock @@ -306,7 +306,7 @@ elif (config.auth_force_edmc_protocol GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object if args.lower().startswith('open("') and args.endswith('")'): - logger.trace(f'args are: {args}') + logger.trace_if('frontier-auth.windows', f'args are: {args}') url = urllib.parse.unquote(args[6:-2]).strip() if url.startswith(self.redirect): logger.debug(f'Message starts with {self.redirect}') @@ -384,7 +384,7 @@ else: # Linux / Run from source def parse(self) -> bool: """Parse a request.""" - logger.trace(f'Got message on path: {self.path}') + logger.trace_if('frontier-auth.http', f'Got message on path: {self.path}') url = urllib.parse.unquote(self.path) if url.startswith('/auth'): logger.debug('Request starts with /auth, sending to protocolhandler.event()') From 79a2003b53107b977d0734711a45b2f24141e966 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:51:28 +0100 Subject: [PATCH 071/316] plugins/eddn: Use `logger.trace_if(...)` --- plugins/eddn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 194a52de..e532c8a8 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -87,6 +87,7 @@ HORIZ_SKU = 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' class EDDN: """EDDN Data export.""" + DEBUG = 'eddn' in debug_senders SERVER = 'https://eddn.edcd.io:4430' if DEBUG: @@ -193,7 +194,7 @@ class EDDN: and msg['message']['commodities'] == [] and r.text == "FAIL: [<ValidationError: '[] is too short'>]" ): - logger.trace("EDDN is still objecting to empty commodities data") + logger.trace_if('plugin.eddn', "EDDN is still objecting to empty commodities data") return # We want to silence warnings otherwise logger.debug( From bc9f2cda4b08add10e5ad048f34375963e61764e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 14:58:04 +0100 Subject: [PATCH 072/316] plugins/edsm: Use `logger.trace_if(...)` This also re-introduces commented-out Locations tracking, using 'journal.locations' as it's related to that. --- plugins/edsm.py | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index 76ac203e..557f43c6 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -40,7 +40,6 @@ DISCARDED_EVENTS_SLEEP = 10 # trace-if events CMDR_EVENTS = 'edsm-cmdr-events' -LOCATION_EVENTS = 'edsm-locations' class This: @@ -407,14 +406,15 @@ def journal_entry( # noqa: C901, CCR001 return this.on_foot = state['OnFoot'] - # if entry['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked'): - # logger.trace(f'''{entry["event"]} -# Commander: {cmdr} -# System: {system} -# Station: {station} -# state: {state!r} -# entry: {entry!r}''' -# ) + if entry['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked'): + logger.trace_if( + 'journal.locations', f'''{entry["event"]} +Commander: {cmdr} +System: {system} +Station: {station} +state: {state!r} +entry: {entry!r}''' + ) # Always update our system address even if we're not currently the provider for system or station, but dont update # on events that contain "future" data, such as FSDTarget if entry['event'] in ('Location', 'Docked', 'CarrierJump', 'FSDJump'): @@ -519,10 +519,11 @@ def journal_entry( # noqa: C901, CCR001 this.queue.put((cmdr, materials)) - # if entry['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked'): - # logger.trace(f'''{entry["event"]} -# Queueing: {entry!r}''' -# ) + if entry['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked'): + logger.trace_if( + 'journal.locations', f'''{entry["event"]} +Queueing: {entry!r}''' + ) logger.trace_if(CMDR_EVENTS, f'"{entry["event"]=}" event, queueing: {cmdr=}') this.queue.put((cmdr, entry)) @@ -656,13 +657,15 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently logger.trace_if(CMDR_EVENTS, f'pending contains:\n{chr(0x0A).join(str(p) for p in pending)}') if any(p for p in pending if p['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked')): - logger.trace_if(LOCATION_EVENTS, "pending has at least one of " + logger.trace_if('journal.locations', "pending has at least one of " "('CarrierJump', 'FSDJump', 'Location', 'Docked')" " and it passed should_send()") for p in pending: if p['event'] in ('Location'): - logger.trace_if(LOCATION_EVENTS, '"Location" event in pending passed should_send(), ' - f'timestamp: {p["timestamp"]}') + logger.trace_if( + 'journal.locations', + f'"Location" event in pending passed should_send(),timestamp: {p["timestamp"]}' + ) creds = credentials(cmdr) # TODO: possibly unbound if creds is None: @@ -683,25 +686,25 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently data_elided = data.copy() data_elided['apiKey'] = '<elided>' logger.trace_if( - LOCATION_EVENTS, + 'journal.locations', "pending has at least one of ('CarrierJump', 'FSDJump', 'Location', 'Docked')" " Attempting API call with the following events:" ) for p in pending: - logger.trace_if(LOCATION_EVENTS, f"Event: {p!r}") + logger.trace_if('journal.locations', f"Event: {p!r}") if p['event'] in ('Location'): logger.trace_if( - LOCATION_EVENTS, + 'journal.locations', f'Attempting API call for "Location" event with timestamp: {p["timestamp"]}' ) logger.trace_if( - LOCATION_EVENTS, f'Overall POST data (elided) is:\n{json.dumps(data_elided, indent=2)}' + 'journal.locations', f'Overall POST data (elided) is:\n{json.dumps(data_elided, indent=2)}' ) r = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT) - # logger.trace(f'API response content: {r.content}') + logger.trace_if('plugin.edsm.api', f'API response content: {r.content!r}') r.raise_for_status() reply = r.json() @@ -720,11 +723,11 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently else: if msg_num // 100 == 1: - # logger.trace('Overall OK') + logger.trace_if('plugin.edsm.api', 'Overall OK') pass elif msg_num // 100 == 5: - # logger.trace('Event(s) not currently processed, but saved for later') + logger.trace_if('plugin.edsm.api', 'Event(s) not currently processed, but saved for later') pass else: From 2754385281a0cf75103e817f56edb81d8879e411 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 15:04:01 +0100 Subject: [PATCH 073/316] monitor: Use `trace_if('journal.startup', ...)` for game load-in --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 31c2a507..debba020 100644 --- a/monitor.py +++ b/monitor.py @@ -24,7 +24,7 @@ from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised from EDMCLogging import get_main_logger logger = get_main_logger() -STARTUP = 'startup' +STARTUP = 'journal.startup' if TYPE_CHECKING: def _(x: str) -> str: From 4433b67c829cb3510ba5b409b20760fff8779499 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 15:06:35 +0100 Subject: [PATCH 074/316] EDMarketConnector: Use `logger.trace_if(...)` Yes, leaving that 'truncating plain logfile' as plain trace. --- Contributing.md | 9 +++++++-- EDMarketConnector.py | 8 ++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Contributing.md b/Contributing.md index 814ff7ca..c74953a1 100644 --- a/Contributing.md +++ b/Contributing.md @@ -416,13 +416,18 @@ In addition to that we utilise one of the user-defined levels as: method provided on `logger`: ```python - logger.trace_if('my-trace-rule', 'my-log-message') + logger.trace_if('journal.event.scan', 'my-log-message') ``` + The string used to identify this tracing should be related to the + **function of the code**, not the particular file, or class, that it is in. + This is so that the same string can be used to trace code that spans more + than one file, class, or other scope. + This would then be triggered by running EDMarketConnector with the appropriate command-line arguments: - EDMarketConnector.py --trace-on my-trace-rule + EDMarketConnector.py --trace-on journal.event.scan Note that you do **not** also need to specify `--trace`, that's implied. diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b2d4cba8..a44a465d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -174,7 +174,7 @@ if __name__ == '__main__': # noqa: C901 def handle_edmc_callback_or_foregrounding() -> None: # noqa: CCR001 """Handle any edmc:// auth callback, else foreground existing window.""" - logger.trace('Begin...') + logger.trace_if('frontier-auth.windows', 'Begin...') if platform == 'win32': @@ -1108,7 +1108,7 @@ class AppWindow(object): entry = monitor.get_entry() if not entry: # This is expected due to some monitor.py code that appends `None` - # logger.trace('No entry from monitor.get_entry()') + logger.trace_if('journal.queue', 'No entry from monitor.get_entry()') return # Update main window @@ -1199,7 +1199,7 @@ class AppWindow(object): return # in CQC if not entry['event'] or not monitor.mode: - # logger.trace('Startup, returning') + logger.trace_if('journal.queue', 'Startup, returning') return # Startup if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started: @@ -1828,7 +1828,7 @@ sys.path: {sys.path}''' ui_scale = 100 config.set('ui_scale', ui_scale) theme.default_ui_scale = root.tk.call('tk', 'scaling') - logger.trace(f'Default tk scaling = {theme.default_ui_scale}') + logger.trace_if('tk', f'Default tk scaling = {theme.default_ui_scale}') theme.startup_ui_scale = ui_scale root.tk.call('tk', 'scaling', theme.default_ui_scale * float(ui_scale) / 100.0) app = AppWindow(root) From 021d3a25faef40451bdbf117f5c664bd06491e20 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 15:20:21 +0100 Subject: [PATCH 075/316] EDMC: Actually set config.trace_on from CL args Else they won't work, duh! --- EDMC.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/EDMC.py b/EDMC.py index 4d7a5dd8..26dc6d7b 100755 --- a/EDMC.py +++ b/EDMC.py @@ -191,6 +191,12 @@ exec_prefix: {sys.exec_prefix} executable: {sys.executable} sys.path: {sys.path}''' ) + if args.trace_on and len(args.trace_on) > 0: + import config as conf_module + + conf_module.trace_on = [x.casefold() for x in args.trace_on] # duplicate the list just in case + for d in conf_module.trace_on: + logger.info(f'marked {d} for TRACE') log_locale('Initial Locale') From 7149f7e792fb0e2e81499de121a20fb5b65c30ca Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 15:20:42 +0100 Subject: [PATCH 076/316] companion: Change trace_if 'capi-query' to 'capi.query' --- companion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index eb4d8584..6ad2bc01 100644 --- a/companion.py +++ b/companion.py @@ -554,7 +554,7 @@ class Session(object): def query(self, endpoint: str) -> CAPIData: # noqa: CCR001 """Perform a query against the specified CAPI endpoint.""" - logger.trace_if('capi-query', f'Performing query for endpoint "{endpoint}"') + logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: if self.login(): return self.query(endpoint) @@ -564,7 +564,7 @@ class Session(object): raise CredentialsError('cannot make a query when unauthorized') try: - logger.trace_if('capi-query', 'Trying...') + logger.trace_if('capi.query', 'Trying...') r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore except requests.ConnectionError as e: From 82ab72a655761923eed272cf71194779ae03c525 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 15:22:35 +0100 Subject: [PATCH 077/316] EDMCLogging: Fix import order --- EDMCLogging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 19dc60f6..c00b7ab9 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -42,12 +42,12 @@ import os import pathlib import tempfile from contextlib import suppress +from fnmatch import fnmatch # So that any warning about accessing a protected member is only in one place. from sys import _getframe as getframe from threading import get_native_id as thread_native_id from traceback import print_exc from typing import TYPE_CHECKING, Tuple, cast -from fnmatch import fnmatch import config as config_mod from config import appcmdname, appname, config From 1480c2d67a7954f76b8ab24bc0422f956cae0329 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 13 Aug 2021 15:29:57 +0100 Subject: [PATCH 078/316] plugins/edsm: Change trace_if string to plugin.edsm.cmdr-events --- plugins/edsm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index 557f43c6..5f23ba23 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -39,7 +39,7 @@ _TIMEOUT = 20 DISCARDED_EVENTS_SLEEP = 10 # trace-if events -CMDR_EVENTS = 'edsm-cmdr-events' +CMDR_EVENTS = 'plugin.edsm.cmdr-events' class This: From a8a4abfe90e6245f333998229cb9bebdd1f8a986 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Fri, 13 Aug 2021 18:06:38 +0300 Subject: [PATCH 079/316] inara.py: trace with `logger.trace_if` only --- plugins/inara.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 024791c1..caa79644 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -13,7 +13,6 @@ from typing import Sequence, Union, cast import requests -import config as conf_module # Necessary to see the same config.trace_on as elsewhere import edmc_data import killswitch import myNotebook as nb # noqa: N813 @@ -1492,8 +1491,7 @@ def new_worker(): ] } logger.info(f'sending {len(data["events"])} events for {creds.cmdr}') - if 'inara-events' in conf_module.trace_on: - logger.trace(f'Events:\n{json.dumps(data)}\n') + logger.trace_if('plugin.inara.events', f'Events:\n{json.dumps(data)}\n') try_send_data(TARGET_URL, data) From ad49ad531ebf1e68582be1825f9d5a77b8442f48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Aug 2021 17:02:58 +0000 Subject: [PATCH 080/316] build(deps-dev): bump flake8-comprehensions from 3.5.0 to 3.6.0 Bumps [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) from 3.5.0 to 3.6.0. - [Release notes](https://github.com/adamchainz/flake8-comprehensions/releases) - [Changelog](https://github.com/adamchainz/flake8-comprehensions/blob/main/HISTORY.rst) - [Commits](https://github.com/adamchainz/flake8-comprehensions/compare/3.5.0...3.6.0) --- updated-dependencies: - dependency-name: flake8-comprehensions dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 066f636d..734264f1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ wheel flake8==3.9.2 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 -flake8-comprehensions==3.5.0 +flake8-comprehensions==3.6.0 flake8-docstrings==1.6.0 isort==5.9.3 flake8-isort==4.0.0 From 01bd63773078dad39c717b68ce7823c1590b4662 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Fri, 13 Aug 2021 21:47:47 +0300 Subject: [PATCH 081/316] Add CL arg `--capi-pretend-down` in order to pretend that CAPI is down --- EDMarketConnector.py | 10 ++++++++++ companion.py | 10 ++++++++++ config.py | 1 + 3 files changed, 21 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b2d4cba8..886bf1a7 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -127,8 +127,18 @@ if __name__ == '__main__': # noqa: C901 nargs='*' ) + parser.add_argument( + '--capi-pretend-down', + help='Force to raise ServerError on any CAPI query', + action='store_true') + args = parser.parse_args() + if args.capi_pretend_down: + import config as conf_module + logger.info('Pretending CAPI is down') + conf_module.capi_pretend_down = True + level_to_set: Optional[int] = None if args.trace or args.trace_on: level_to_set = logging.TRACE # type: ignore # it exists diff --git a/companion.py b/companion.py index f2f571b6..7f55fc7f 100644 --- a/companion.py +++ b/companion.py @@ -25,6 +25,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, OrderedDic import requests from config import appname, appversion, config +import config as conf_module from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor @@ -276,6 +277,9 @@ class Auth(object): logger.debug('Attempting refresh with Frontier...') try: + if conf_module.capi_pretend_down: + raise ServerError(_('Pretending CAPI is down')) + r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=auth_timeout) if r.status_code == requests.codes.ok: data = r.json() @@ -360,6 +364,9 @@ class Auth(object): # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True + if conf_module.capi_pretend_down: + raise ServerError(_('Pretending CAPI is down')) + r = self.session.post(SERVER_AUTH + URL_TOKEN, data=request_data, timeout=auth_timeout) data_token = r.json() if r.status_code == requests.codes.ok: @@ -565,6 +572,9 @@ class Session(object): try: logger.trace('Trying...') + if conf_module.capi_pretend_down: + raise ServerError('Pretending CAPI is down') + r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore except requests.ConnectionError as e: diff --git a/config.py b/config.py index 012eb84d..d1cfd403 100644 --- a/config.py +++ b/config.py @@ -45,6 +45,7 @@ debug_senders: List[str] = [] # *all* if only interested in some things. trace_on: List[str] = [] +capi_pretend_down: bool = False # This must be done here in order to avoid an import cycle with EDMCLogging. # Other code should use EDMCLogging.get_main_logger if os.getenv("EDMC_NO_UI"): From 92b131f81a079b20530cba97250f78747f406389 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Fri, 13 Aug 2021 23:07:18 +0300 Subject: [PATCH 082/316] companion.py: reorder `conf_module` import for isort --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index 5b5842e1..43722007 100644 --- a/companion.py +++ b/companion.py @@ -24,8 +24,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, OrderedDic import requests -from config import appname, appversion, config import config as conf_module +from config import appname, appversion, config from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor From 2e90a1bbe9386673b703c885fb2deda1643acb3e Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 16 Aug 2021 14:40:46 +0300 Subject: [PATCH 083/316] Remove raise CAPI pretend down on auth requests, add endpoint for which CAPI pretend down raising --- companion.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/companion.py b/companion.py index 43722007..eadfb1d9 100644 --- a/companion.py +++ b/companion.py @@ -277,9 +277,6 @@ class Auth(object): logger.debug('Attempting refresh with Frontier...') try: - if conf_module.capi_pretend_down: - raise ServerError(_('Pretending CAPI is down')) - r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=auth_timeout) if r.status_code == requests.codes.ok: data = r.json() @@ -364,9 +361,6 @@ class Auth(object): # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True - if conf_module.capi_pretend_down: - raise ServerError(_('Pretending CAPI is down')) - r = self.session.post(SERVER_AUTH + URL_TOKEN, data=request_data, timeout=auth_timeout) data_token = r.json() if r.status_code == requests.codes.ok: @@ -573,7 +567,7 @@ class Session(object): try: logger.trace_if('capi.query', 'Trying...') if conf_module.capi_pretend_down: - raise ServerError('Pretending CAPI is down') + raise ServerError(f'Pretending CAPI is down for {endpoint} endpoint') r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore From b30b25216bf1db86dd3a59f0a745423c67e8c939 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 16 Aug 2021 15:24:01 +0300 Subject: [PATCH 084/316] companion.py: change raising exception when pretending CAPI down --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index eadfb1d9..f9ea3b37 100644 --- a/companion.py +++ b/companion.py @@ -567,7 +567,7 @@ class Session(object): try: logger.trace_if('capi.query', 'Trying...') if conf_module.capi_pretend_down: - raise ServerError(f'Pretending CAPI is down for {endpoint} endpoint') + raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore From 8d6fae6ef6faac6a8e0e47908f1813b27cb3ac94 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 16 Aug 2021 15:28:13 +0300 Subject: [PATCH 085/316] EDMarketConnector.py: style fix --- EDMarketConnector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8385af2b..ab893b50 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -130,7 +130,8 @@ if __name__ == '__main__': # noqa: C901 parser.add_argument( '--capi-pretend-down', help='Force to raise ServerError on any CAPI query', - action='store_true') + action='store_true' + ) args = parser.parse_args() From e6c6f7f1554b46bc86ffdf754d19a236b76c482a Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Mon, 16 Aug 2021 15:30:14 +0300 Subject: [PATCH 086/316] companion.py: Move check for CAPI pretend down --- companion.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/companion.py b/companion.py index f9ea3b37..08c92b7d 100644 --- a/companion.py +++ b/companion.py @@ -564,11 +564,11 @@ class Session(object): logger.error('cannot make a query when unauthorized') raise CredentialsError('cannot make a query when unauthorized') - try: - logger.trace_if('capi.query', 'Trying...') - if conf_module.capi_pretend_down: - raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') + logger.trace_if('capi.query', 'Trying...') + if conf_module.capi_pretend_down: + raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') + try: r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore except requests.ConnectionError as e: From 1f7dad024d76d33536fbe398816c02c0a8c9f446 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Aug 2021 17:05:53 +0000 Subject: [PATCH 087/316] build(deps-dev): bump flake8-comprehensions from 3.6.0 to 3.6.1 Bumps [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) from 3.6.0 to 3.6.1. - [Release notes](https://github.com/adamchainz/flake8-comprehensions/releases) - [Changelog](https://github.com/adamchainz/flake8-comprehensions/blob/main/HISTORY.rst) - [Commits](https://github.com/adamchainz/flake8-comprehensions/compare/3.6.0...3.6.1) --- updated-dependencies: - dependency-name: flake8-comprehensions dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 734264f1..7e692601 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ wheel flake8==3.9.2 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 -flake8-comprehensions==3.6.0 +flake8-comprehensions==3.6.1 flake8-docstrings==1.6.0 isort==5.9.3 flake8-isort==4.0.0 From 1581a3bdd7bcae16122f9be2001bde36b0c771b1 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 17 Aug 2021 13:54:38 +0300 Subject: [PATCH 088/316] plugins/edsm.py: fix exception on logger.trace_on, wrong datatype to subtitle --- plugins/edsm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/edsm.py b/plugins/edsm.py index 5f23ba23..2f082c2f 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -685,6 +685,8 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently if any(p for p in pending if p['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked')): data_elided = data.copy() data_elided['apiKey'] = '<elided>' + data_elided['message'] = data_elided['message'].decode('utf-8') + data_elided['commanderName'] = data_elided['commanderName'].decode('utf-8') logger.trace_if( 'journal.locations', "pending has at least one of ('CarrierJump', 'FSDJump', 'Location', 'Docked')" From 3e38965ef1e03045c7af8c5df5e8feace65f0ef4 Mon Sep 17 00:00:00 2001 From: norohind <60548839+norohind@users.noreply.github.com> Date: Tue, 17 Aug 2021 18:12:16 +0300 Subject: [PATCH 089/316] Output > 'File Location' should always be available --- prefs.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/prefs.py b/prefs.py index 6c900863..c403023b 100644 --- a/prefs.py +++ b/prefs.py @@ -1015,12 +1015,6 @@ class PreferencesDialog(tk.Toplevel): self.out_td_button['state'] = tk.NORMAL self.out_ship_button['state'] = tk.NORMAL - local = any((self.out_td.get(), self.out_csv.get(), self.out_ship.get())) - self.out_auto_button['state'] = tk.NORMAL if local and logvalid else tk.DISABLED - self.outdir_label['state'] = tk.NORMAL if local else tk.DISABLED - self.outbutton['state'] = tk.NORMAL if local else tk.DISABLED - self.outdir_entry['state'] = tk.NORMAL if local else tk.DISABLED - def filebrowse(self, title, pathvar): """ Open a directory selection dialog. From 11b1799d09af82052b986df3c3d4732edee3b198 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Aug 2021 17:02:57 +0000 Subject: [PATCH 090/316] build(deps): bump watchdog from 2.1.3 to 2.1.4 Bumps [watchdog](https://github.com/gorakhargosh/watchdog) from 2.1.3 to 2.1.4. - [Release notes](https://github.com/gorakhargosh/watchdog/releases) - [Changelog](https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst) - [Commits](https://github.com/gorakhargosh/watchdog/compare/v2.1.3...v2.1.4) --- updated-dependencies: - dependency-name: watchdog dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bb1e7e4f..e7b9bd39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ certifi==2021.5.30 requests==2.26.0 -watchdog==2.1.3 +watchdog==2.1.4 # Commented out because this doesn't package well with py2exe # infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep From 2583d07baf4ed362f6f8062136bd7fa72fc2d798 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 3 Jul 2021 15:17:31 +0200 Subject: [PATCH 091/316] Added additional data fields to killswitches These fields will be used for various things moving forward. The currently intended use is to redact fields when sending data to inara/edsm/eddn in case Frontier accidentally includes something that is unwanted --- docs/Killswitches.md | 40 ++++++++----- killswitch.py | 139 +++++++++++++++++++++++++++++++++---------- 2 files changed, 133 insertions(+), 46 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 9e08feb5..a2c99c31 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -12,23 +12,33 @@ Killswitches are stored in a JSON file that is queried by EDMC on startup. The f | `last_updated` | `string` | When last the kill switches were updated (for human use only) | | `kill_switches` | `array` | The kill switches this file contains (expanded below) | -The `kill_switches` array contains kill switch objects. Each contains two fields: +The `kill_switches` array contains kill switch objects. Each contains the following fields: + +| Key | Type | Description | +| --------: | :-------------------------: | :--------------------------------------------------------------------------- | +| `version` | `version spec` | The version of EDMC these kill switches apply to (Must be valid semver spec) | +| `kills` | `Dict[str, Dict[str, Any]]` | The different keys to disable, and the reason for the disable | + +Each entry in `kills` contains a `reason` and `additional_data` field. `reason` is self explanatory, however +additional_data can contain various things. They are outlaid below: +| Key | Type | Description | +| -------------: | :---------: | :-------------------------------------------------------------------- | +| `redact_field` | `List[str]` | A list of fields in the matching event to be redacted, if they exist. | -| Key | Type | Description | -| --------: | :--------------: | :--------------------------------------------------------------------------- | -| `version` | `version spec` | The version of EDMC these kill switches apply to (Must be valid semver spec) | -| `kills` | `Dict[str, str]` | The different keys to disable, and the reason for the disable | An example follows: ```json { - "version": 1, - "last_updated": "19 October 2020", + "version": 2, + "last_updated": "3 July 2021", "kill_switches": [ { "version": "1.0.0", "kills": { - "plugins.eddn.send": "some reason" + "plugins.eddn.send": { + "reason": "some reason", + "additional_data": {} + } } } ] @@ -45,15 +55,17 @@ Plugins may use the killswitch system simply by hosting their own version of the using `killswitch.get_kill_switches(target='https://example.com/myplugin_killswitches.json')`. The returned object can be used to query the kill switch set, see the docstrings for more information on specifying versions. +The version of the JSON file will be automatically upgraded if possible by the code KillSwitch code. + ## Currently supported killswitch strings The current recognised (to EDMC and its internal plugins) killswitch strings are as follows: -| Kill Switch | Description | -| :--------------------------------------------------- | :------------------------------------------------------------------------------------------- | -| `plugins.eddn.send` | Disables all use of the send method on EDDN (effectively disables EDDN updates) | -| `plugins.(eddn\|inara\|edsm\|eddb).journal` | Disables all journal processing for EDDN/EDSM/INARA | -| `plugins.(edsm\|inara).worker` | Disables the EDSM/INARA worker thread (effectively disables updates) (does not close thread) | -| `plugins.(eddn\|inara\|edsm).journal.event.$eventname`| Specific events to disable processing for | +| Kill Switch | Description | +| :----------------------------------------------------- | :------------------------------------------------------------------------------------------- | +| `plugins.eddn.send` | Disables all use of the send method on EDDN (effectively disables EDDN updates) | +| `plugins.(eddn\|inara\|edsm\|eddb).journal` | Disables all journal processing for EDDN/EDSM/INARA | +| `plugins.(edsm\|inara).worker` | Disables the EDSM/INARA worker thread (effectively disables updates) (does not close thread) | +| `plugins.(eddn\|inara\|edsm).journal.event.$eventname` | Specific events to disable processing for | ## File location diff --git a/killswitch.py b/killswitch.py index aca020aa..9d01d569 100644 --- a/killswitch.py +++ b/killswitch.py @@ -1,8 +1,12 @@ """Fetch kill switches from EDMC Repo.""" -from typing import Dict, List, NamedTuple, Optional, Union, cast +from __future__ import annotations +from os import kill +from typing import Any, Dict, List, NamedTuple, Optional, TypedDict, Union, cast import requests import semantic_version +from copy import deepcopy +from semantic_version.base import Version import config import EDMCLogging @@ -10,31 +14,57 @@ import EDMCLogging logger = EDMCLogging.get_main_logger() DEFAULT_KILLSWITCH_URL = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/killswitches.json' +CURRENT_KILLSWITCH_VERSION = 2 _current_version: semantic_version.Version = config.appversion_nobuild() -class KillSwitch(NamedTuple): +class SingleKill(NamedTuple): + """A single KillSwitch. Possibly with additional data.""" + + match: str + reason: str + additional_data: Dict[str, Any] + + +class KillSwitches(NamedTuple): """One version's set of kill switches.""" version: semantic_version.SimpleSpec - kills: Dict[str, str] + kills: Dict[str, SingleKill] + + @staticmethod + def from_dict(data: KillSwitchSetJSON) -> KillSwitches: + """Create a KillSwitches instance from a dictionary.""" + ks = {} + + for match, ks_data in data['kills'].items(): + ks[match] = SingleKill( + match=match, reason=ks_data['reason'], additional_data=ks_data.get('additional_data', {}) + ) + + return KillSwitches(version=semantic_version.SimpleSpec(data['version']), kills=ks) class DisabledResult(NamedTuple): """DisabledResult is the result returned from various is_disabled calls.""" disabled: bool - reason: str + kill: Optional[SingleKill] + + @ property + def reason(self) -> str: + """Reason provided for why this killswitch exists.""" + return self.kill.reason if self.kill is not None else "" class KillSwitchSet: """Queryable set of kill switches.""" - def __init__(self, kill_switches: List[KillSwitch]) -> None: + def __init__(self, kill_switches: List[KillSwitches]) -> None: self.kill_switches = kill_switches - def get_disabled(self, id: str, *, version=_current_version) -> DisabledResult: + def get_disabled(self, id: str, *, version: Union[Version, str] = _current_version) -> DisabledResult: """ Return whether or not the given feature ID is disabled by a killswitch for the given version. @@ -46,9 +76,9 @@ class KillSwitchSet: if version not in ks.version: continue - return DisabledResult(id in ks.kills, ks.kills.get(id, "")) + return DisabledResult(id in ks.kills, ks.kills.get(id, None)) - return DisabledResult(False, "") + return DisabledResult(False, None) def is_disabled(self, id: str, *, version: semantic_version.Version = _current_version) -> bool: """Return whether or not a given feature ID is disabled for the given version.""" @@ -58,7 +88,7 @@ class KillSwitchSet: """Return a reason for why the given id is disabled for the given version, if any.""" return self.get_disabled(id, version=version).reason - def kills_for_version(self, version: semantic_version.Version = _current_version) -> List[KillSwitch]: + def kills_for_version(self, version: semantic_version.Version = _current_version) -> List[KillSwitches]: """ Get all killswitch entries that apply to the given version. @@ -76,17 +106,23 @@ class KillSwitchSet: return f'KillSwitchSet(kill_switches={self.kill_switches!r})' -KILL_SWITCH_JSON = List[Dict[str, Union[str, List[str]]]] -KILL_SWITCH_JSON_DICT = Dict[ - str, Union[ - str, # Last updated - int, # Version - KILL_SWITCH_JSON # kills - ] -] +class SingleKillSwitchJSON(TypedDict): # noqa: D101 + reason: str + additional_data: Dict[str, Any] -def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> Optional[KILL_SWITCH_JSON_DICT]: +class KillSwitchSetJSON(TypedDict): # noqa: D101 + version: str + kills: Dict[str, SingleKillSwitchJSON] + + +class KillSwitchJSONFile(TypedDict): # noqa: D101 + version: int + last_updated: str + kill_switches: List[KillSwitchSetJSON] + + +def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> Optional[KillSwitchJSONFile]: """ Fetch the JSON representation of our kill switches. @@ -108,34 +144,70 @@ def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> Optional[KILL_SWITCH_J return data -def parse_kill_switches(data: KILL_SWITCH_JSON_DICT) -> List[KillSwitch]: +class _KillSwitchV1(TypedDict): + version: str + kills: Dict[str, str] + + +class _KillSwitchJSONFileV1(TypedDict): + version: int + last_updated: str + kill_switches: List[_KillSwitchV1] + + +def _upgrade_kill_switch_dict(data: KillSwitchJSONFile) -> KillSwitchJSONFile: + version = data['version'] + if version == CURRENT_KILLSWITCH_VERSION: + return data + + if version == 1: + logger.info('Got an old version killswitch file (v1) upgrading!') + to_return: KillSwitchJSONFile = deepcopy(data) + data_v1 = cast(_KillSwitchJSONFileV1, data) + # reveal_type(to_return['kill_switches']) + + to_return['kill_switches'] = [ + cast(KillSwitchSetJSON, { # I need to cheat here a touch. It is this I promise + 'version': d['version'], + 'kills': { + match: {'reason': reason, 'additional_data': {}} for match, reason in d['kills'].items() + } + }) + for d in data_v1['kill_switches'] + ] + + to_return['version'] = CURRENT_KILLSWITCH_VERSION + + return to_return + + return data + + +def parse_kill_switches(data: KillSwitchJSONFile) -> List[KillSwitches]: """ Parse kill switch dict to List of KillSwitches. :param data: dict containing raw killswitch data :return: a list of all provided killswitches """ + data = _upgrade_kill_switch_dict(data) last_updated = data['last_updated'] ks_version = data['version'] logger.info(f'Kill switches last updated {last_updated}') - if ks_version != 1: - logger.warning(f'Unknown killswitch version {ks_version}. Bailing out') + if ks_version != CURRENT_KILLSWITCH_VERSION: + logger.warning(f'Unknown killswitch version {ks_version} (expected {CURRENT_KILLSWITCH_VERSION}). Bailing out') return [] - kill_switches = cast(KILL_SWITCH_JSON, data['kill_switches']) - out: List[KillSwitch] = [] - + kill_switches = data['kill_switches'] + out = [] for idx, ks_data in enumerate(kill_switches): try: - ver = semantic_version.SimpleSpec(ks_data['version']) + ks = KillSwitches.from_dict(ks_data) + out.append(ks) - except ValueError as e: - logger.warning(f'could not parse killswitch idx {idx}: {e}') - continue - - ks = KillSwitch(version=ver, kills=cast(Dict[str, str], ks_data['kills'])) - out.append(ks) + except Exception as e: + logger.exception(f'Could not parse killswitch idx {idx}: {e}') return out @@ -169,6 +241,9 @@ def setup_main_list(): global active active = KillSwitchSet(parse_kill_switches(data)) + logger.trace('Active Killswitches:') + for v in active.kill_switches: + logger.trace(v) def get_disabled(id: str, *, version: semantic_version.Version = _current_version) -> DisabledResult: @@ -190,6 +265,6 @@ def get_reason(id: str, *, version: semantic_version.Version = _current_version) return active.get_reason(id, version=version) -def kills_for_version(version: semantic_version.Version = _current_version) -> List[KillSwitch]: +def kills_for_version(version: semantic_version.Version = _current_version) -> List[KillSwitches]: """Query the global KillSwitchSet for kills matching a particular version.""" return active.kills_for_version(version) From 4c9896569bcf88f8efb4f0aca50001d7e548243f Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 3 Jul 2021 15:53:12 +0200 Subject: [PATCH 092/316] added inara support for new delete_fields directive --- plugins/inara.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index caa79644..ff4fb55d 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -341,8 +341,13 @@ def journal_entry( # noqa: C901, CCR001 elif (ks := killswitch.get_disabled(f'plugins.inara.journal.event.{entry["event"]}')).disabled: logger.warning(f'event {entry["event"]} processing has been disabled via killswitch: {ks.reason}') - # this can and WILL break state, but if we're concerned about it sending bad data, we'd disable globally anyway - return '' + if ks.kill is None or 'delete_fields' not in ks.kill.additional_data: + # this can and WILL break state, but if we're concerned about it sending bad data, we'd disable globally anyway + return '' + + elif 'delete_fields' in ks.kill.additional_data: + for field in ks.kill.additional_data['delete_fields']: + entry.pop(field, None) this.on_foot = state['OnFoot'] event_name: str = entry['event'] @@ -1461,6 +1466,25 @@ def new_add_event( this.events[key].append(Event(name, timestamp, data)) +def clean_event_list(event_list: List[Event]) -> list[Event]: + """Check for killswitched events and remove or modify them as requested.""" + out = [] + + for e in event_list: + ks = killswitch.get_disabled(f'plugins.inara.worker.{e.name}') + if not ks.disabled: + out.append(e) + + if ks.kill is not None and 'delete_fields' in ks.kill.additional_data: + # its disabled, but... we have + for field in ks.kill.additional_data['delete_fields']: + cast(Dict[str, Any], e.data).pop(field, None) + + out.append(e) + + return out + + def new_worker(): """ Handle sending events to the Inara API. @@ -1475,6 +1499,7 @@ def new_worker(): continue for creds, event_list in events.items(): + event_list = clean_event_list(event_list) if not event_list: continue From 6c56b9851aef6385cd603b35053ad93076738509 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 3 Jul 2021 15:53:45 +0200 Subject: [PATCH 093/316] made redact_fields into delete_fields otherwise there are type issues --- docs/Killswitches.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index a2c99c31..3b9e2e6c 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -21,9 +21,9 @@ The `kill_switches` array contains kill switch objects. Each contains the follow Each entry in `kills` contains a `reason` and `additional_data` field. `reason` is self explanatory, however additional_data can contain various things. They are outlaid below: -| Key | Type | Description | -| -------------: | :---------: | :-------------------------------------------------------------------- | -| `redact_field` | `List[str]` | A list of fields in the matching event to be redacted, if they exist. | +| Key | Type | Description | +| --------------: | :---------: | :------------------------------------------------------------------- | +| `delete_fields` | `List[str]` | A list of fields in the matching event to be removed, if they exist. | An example follows: @@ -60,12 +60,13 @@ The version of the JSON file will be automatically upgraded if possible by the c ## Currently supported killswitch strings The current recognised (to EDMC and its internal plugins) killswitch strings are as follows: -| Kill Switch | Description | -| :----------------------------------------------------- | :------------------------------------------------------------------------------------------- | -| `plugins.eddn.send` | Disables all use of the send method on EDDN (effectively disables EDDN updates) | -| `plugins.(eddn\|inara\|edsm\|eddb).journal` | Disables all journal processing for EDDN/EDSM/INARA | -| `plugins.(edsm\|inara).worker` | Disables the EDSM/INARA worker thread (effectively disables updates) (does not close thread) | -| `plugins.(eddn\|inara\|edsm).journal.event.$eventname` | Specific events to disable processing for | +| Kill Switch | Description | +| :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | +| `plugins.eddn.send` | Disables all use of the send method on EDDN (effectively disables EDDN updates) | +| `plugins.(eddn\|inara\|edsm\|eddb).journal` | Disables all journal processing for EDDN/EDSM/INARA | +| `plugins.(edsm\|inara).worker` | Disables the EDSM/INARA worker thread (effectively disables updates) (does not close thread) | +| `plugins.(edsm\|inara).worker.$eventname` | Disables the EDSM/INARA worker for the given eventname, OR if delete_fields exists, removes the fields from the event | +| `plugins.(eddn\|inara\|edsm).journal.event.$eventname` | Specific events to disable processing for. OR, if delete_fields exists as additional_data, the fields listed will be removed before processing | ## File location From 39eaf608a29c90a613a70d49f6d5445d23fc4c5d Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 7 Jul 2021 13:29:13 +0200 Subject: [PATCH 094/316] Scratch that, lets do this in an extensible manner Keys now exist directly on Kills entries, those keys are only additive, if EDMC doesnt understand something it just drops the entire event etc. --- docs/Killswitches.md | 39 ++++++----- killswitch.py | 155 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 159 insertions(+), 35 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 3b9e2e6c..91fbb26e 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -6,24 +6,27 @@ EDMarketConnector implements a Kill Switch system that allows us to disable feat Killswitches are stored in a JSON file that is queried by EDMC on startup. The format is as follows: -| Key | Type | Description | -| --------------: | :------: | :------------------------------------------------------------ | -| `version` | `int` | the version of the Kill Switch JSON file, always 1 | -| `last_updated` | `string` | When last the kill switches were updated (for human use only) | -| `kill_switches` | `array` | The kill switches this file contains (expanded below) | +| Key | Type | Description | +| --------------: | :------: | :------------------------------------------------------------------------------------------- | +| `version` | `int` | the version of the Kill Switch JSON file, always 2, 1 exists and will be upgraded if needed. | +| `last_updated` | `string` | When last the kill switches were updated (for human use only) | +| `kill_switches` | `array` | The kill switches this file contains (expanded below) | The `kill_switches` array contains kill switch objects. Each contains the following fields: -| Key | Type | Description | -| --------: | :-------------------------: | :--------------------------------------------------------------------------- | -| `version` | `version spec` | The version of EDMC these kill switches apply to (Must be valid semver spec) | -| `kills` | `Dict[str, Dict[str, Any]]` | The different keys to disable, and the reason for the disable | +| Key | Type | Description | +| --------: | :--------------------: | :--------------------------------------------------------------------------- | +| `version` | `version spec` | The version of EDMC these kill switches apply to (Must be valid semver spec) | +| `kills` | `Dict[str, Dict[...]]` | The various keys disabled -> definition of the killswitch behaviour | -Each entry in `kills` contains a `reason` and `additional_data` field. `reason` is self explanatory, however -additional_data can contain various things. They are outlaid below: -| Key | Type | Description | -| --------------: | :---------: | :------------------------------------------------------------------- | -| `delete_fields` | `List[str]` | A list of fields in the matching event to be removed, if they exist. | +Each entry in `kills` must contain at least a `reason` field describing why the killswitch was added. EDMC will show +this to the user (for internal killswitches, anyway). +| Key (* = required) | Type | Description | +| -----------------: | :--------------: | :-------------------------------------------------------------------------------------------- | +| `reason`* | `str` | The reason that this killswitch was added | +| `delete_fields` | `List[str]` | A list of fields in the matching event to be removed, if they exist. | +| `set_fields` | `Dict[str, Any]` | A map of key -> contents to update (or overwrite) existing data with | +| `redact_fields` | `List[str]` | A list of fields to redact. This is equivalent to setting the fields to the string "REDACTED" | An example follows: @@ -37,7 +40,12 @@ An example follows: "kills": { "plugins.eddn.send": { "reason": "some reason", - "additional_data": {} + "delete_fields": ["world_domination_plans"], + "set_fields": { + "bad_bases_for_systems_of_government": ["Strange women lying in ponds distributing swords"], + "ruler_map": {"emperor": "scimitar"} + }, + "redact_fields": ["relation_to_thargoids"] } } } @@ -59,6 +67,7 @@ The version of the JSON file will be automatically upgraded if possible by the c ## Currently supported killswitch strings + <!-- TODO: update this with new behaviour for various fields --> The current recognised (to EDMC and its internal plugins) killswitch strings are as follows: | Kill Switch | Description | | :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/killswitch.py b/killswitch.py index 9d01d569..998db466 100644 --- a/killswitch.py +++ b/killswitch.py @@ -1,11 +1,11 @@ """Fetch kill switches from EDMC Repo.""" from __future__ import annotations -from os import kill -from typing import Any, Dict, List, NamedTuple, Optional, TypedDict, Union, cast +from copy import deepcopy + +from typing import Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, TypedDict, Union, cast import requests import semantic_version -from copy import deepcopy from semantic_version.base import Version import config @@ -13,18 +13,120 @@ import EDMCLogging logger = EDMCLogging.get_main_logger() -DEFAULT_KILLSWITCH_URL = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/killswitches.json' +OLD_KILLSWITCH_URL = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/killswitches.json' +DEFAULT_KILLSWITCH_URL = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/killswitches_v2.json' CURRENT_KILLSWITCH_VERSION = 2 _current_version: semantic_version.Version = config.appversion_nobuild() class SingleKill(NamedTuple): - """A single KillSwitch. Possibly with additional data.""" + """A single KillSwitch. Possibly with additional rules.""" match: str reason: str - additional_data: Dict[str, Any] + redact_fields: Optional[List[str]] + delete_fields: Optional[List[str]] + set_fields: Optional[Dict[str, Any]] + + @property + def has_rules(self) -> bool: + """Return whether or not this SingleKill can apply rules to a dict to make it safe to use.""" + return any(x is not None for x in (self.redact_fields, self.delete_fields, self.set_fields)) + + def apply_rules(self, target: Dict[str, Any]): + """ + Apply the rules this SingleKill instance has to make some data okay to send. + + Note that this MODIFIES DATA IN PLACE. + + :param target: data to apply a rule to + """ + # fields that contain . might be going deeper into the dict. check here FIRST to see if they exist at the top + # level, if not, then work our way down + + for key in (self.redact_fields if self.redact_fields is not None else []): + _deep_apply(target, key, "REDACTED") + + for key in (self.delete_fields if self.delete_fields is not None else []): + _deep_apply(target, key, delete=True) + + for key, value in (self.set_fields if self .set_fields is not None else {}).items(): + _deep_apply(target, key, value) + + +def _apply(target: Union[MutableMapping, MutableSequence], key: str, to_set: Any = None, delete: bool = False): + """ + Set or delete the given target key on the given target. + + :param target: The thing to set data on + :param key: the key or index to set the data to + :param to_set: the data to set, if any, defaults to None + :param delete: whether or not to delete the key or index, defaults to False + :raises ValueError: when an unexpected target type is passed + """ + if isinstance(target, MutableMapping): + if delete: + target.pop(key, None) + else: + target[key] = to_set + + elif isinstance(target, MutableSequence): + if (idx := _get_int(key)) is not None: + if delete: + if len(target) > idx: + target.pop(idx) + + elif len(target) == idx: + target.append(to_set) + + else: + target[idx] = to_set # this can raise, that's fine + + else: + raise ValueError(f'Dont know how to apply data to {type(target)} {target!r}') + + +def _deep_apply(target: dict[str, Any], path: str, to_set=None, delete=False): + """ + Set the given path to the given value, if it exists. + + if the path has dots (ascii period -- '.'), it will be successively split if possible for deeper indices into + target + + :param target: the dict to modify + :param to_set: the data to set, defaults to None + :param delete: whether or not to delete the key rather than set it + """ + current: Union[MutableMapping, MutableSequence] = target + key: str = "" + while '.' in path: + if path in current: + # it exists on this level, dont go further + break + + key, _, path = path.partition('.') + + if isinstance(current, Mapping): + current = current[key] + + elif isinstance(current, Sequence): + if (target_idx := _get_int(key)) is not None: + current = current[target_idx] + else: + raise ValueError(f'Cannot index sequence with non-int key {key!r}') + + else: + raise ValueError(f'Dont know how to index a {type(current)} ({current!r})') + + _apply(current, path, to_set, delete) + + +def _get_int(s: str) -> Optional[int]: + try: + return int(s) + except ValueError: + return None class KillSwitches(NamedTuple): @@ -40,7 +142,11 @@ class KillSwitches(NamedTuple): for match, ks_data in data['kills'].items(): ks[match] = SingleKill( - match=match, reason=ks_data['reason'], additional_data=ks_data.get('additional_data', {}) + match=match, + reason=ks_data['reason'], + redact_fields=ks_data.get('redact_fields'), + set_fields=ks_data.get('set_fields'), + delete_fields=ks_data.get('delete_fields') ) return KillSwitches(version=semantic_version.SimpleSpec(data['version']), kills=ks) @@ -52,7 +158,7 @@ class DisabledResult(NamedTuple): disabled: bool kill: Optional[SingleKill] - @ property + @property def reason(self) -> str: """Reason provided for why this killswitch exists.""" return self.kill.reason if self.kill is not None else "" @@ -106,9 +212,14 @@ class KillSwitchSet: return f'KillSwitchSet(kill_switches={self.kill_switches!r})' -class SingleKillSwitchJSON(TypedDict): # noqa: D101 +class BaseSingleKillSwitch(TypedDict): # noqa: D101 reason: str - additional_data: Dict[str, Any] + + +class SingleKillSwitchJSON(BaseSingleKillSwitch, total=False): # noqa: D101 + redact_fields: list[str] # set fields to "REDACTED" + delete_fields: list[str] # remove fields entirely + set_fields: dict[str, Any] # set fields to given data class KillSwitchSetJSON(TypedDict): # noqa: D101 @@ -137,7 +248,7 @@ def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> Optional[KillSwitchJSO logger.warning(f"Failed to get kill switches, data was invalid: {e}") return None - except (requests.exceptions.BaseHTTPError, requests.exceptions.ConnectionError) as e: + except (requests.exceptions.BaseHTTPError, requests.exceptions.ConnectionError) as e: # type: ignore logger.warning(f"unable to connect to {target!r}: {e}") return None @@ -164,13 +275,12 @@ def _upgrade_kill_switch_dict(data: KillSwitchJSONFile) -> KillSwitchJSONFile: logger.info('Got an old version killswitch file (v1) upgrading!') to_return: KillSwitchJSONFile = deepcopy(data) data_v1 = cast(_KillSwitchJSONFileV1, data) - # reveal_type(to_return['kill_switches']) to_return['kill_switches'] = [ cast(KillSwitchSetJSON, { # I need to cheat here a touch. It is this I promise 'version': d['version'], 'kills': { - match: {'reason': reason, 'additional_data': {}} for match, reason in d['kills'].items() + match: {'reason': reason} for match, reason in d['kills'].items() } }) for d in data_v1['kill_switches'] @@ -180,7 +290,7 @@ def _upgrade_kill_switch_dict(data: KillSwitchJSONFile) -> KillSwitchJSONFile: return to_return - return data + raise ValueError(f'Unknown Killswitch version {data["version"]}') def parse_kill_switches(data: KillSwitchJSONFile) -> List[KillSwitches]: @@ -212,7 +322,7 @@ def parse_kill_switches(data: KillSwitchJSONFile) -> List[KillSwitches]: return out -def get_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> Optional[KillSwitchSet]: +def get_kill_switches(target=DEFAULT_KILLSWITCH_URL, fallback: Optional[str] = None) -> Optional[KillSwitchSet]: """ Get a kill switch set object. @@ -220,8 +330,13 @@ def get_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> Optional[KillSwitchSet]: :return: the KillSwitchSet for the URL, or None if there was an error """ if (data := fetch_kill_switches(target)) is None: - logger.warning('could not get killswitches') - return None + if fallback is not None: + logger.warning('could not get killswitches, trying fallback') + data = fetch_kill_switches(fallback) + + if data is None: + logger.warning('Could not get killswitches.') + return None return KillSwitchSet(parse_kill_switches(data)) @@ -235,13 +350,13 @@ def setup_main_list(): Plugins should NOT call this EVER. """ - if (data := fetch_kill_switches()) is None: + if (data := get_kill_switches(DEFAULT_KILLSWITCH_URL, OLD_KILLSWITCH_URL)) is None: logger.warning("Unable to fetch kill switches. Setting global set to an empty set") return global active - active = KillSwitchSet(parse_kill_switches(data)) - logger.trace('Active Killswitches:') + active = data + logger.trace(f'{len(active.kill_switches)} Active Killswitches:') for v in active.kill_switches: logger.trace(v) From feb05c60d0c55046eab6c00819d28ab767b902e1 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 7 Jul 2021 14:45:05 +0200 Subject: [PATCH 095/316] guard clause Sequence indexing --- killswitch.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/killswitch.py b/killswitch.py index 998db466..d0b6c09e 100644 --- a/killswitch.py +++ b/killswitch.py @@ -1,8 +1,10 @@ """Fetch kill switches from EDMC Repo.""" from __future__ import annotations -from copy import deepcopy -from typing import Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, TypedDict, Union, cast +from copy import deepcopy +from typing import ( + Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, TypedDict, Union, cast +) import requests import semantic_version @@ -72,16 +74,18 @@ def _apply(target: Union[MutableMapping, MutableSequence], key: str, to_set: Any target[key] = to_set elif isinstance(target, MutableSequence): - if (idx := _get_int(key)) is not None: - if delete: - if len(target) > idx: - target.pop(idx) + if (idx := _get_int(key)) is None: + raise ValueError(f'Cannot use string {key!r} as int for index into Sequence') - elif len(target) == idx: - target.append(to_set) + if delete: + if len(target) > idx: + target.pop(idx) - else: - target[idx] = to_set # this can raise, that's fine + elif len(target) == idx: + target.append(to_set) + + else: + target[idx] = to_set # this can raise, that's fine else: raise ValueError(f'Dont know how to apply data to {type(target)} {target!r}') From 366da05cbaeca84229227a64b657d5e41f27bd15 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 7 Jul 2021 16:27:18 +0200 Subject: [PATCH 096/316] added utility methods for using the new actions --- killswitch.py | 56 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/killswitch.py b/killswitch.py index d0b6c09e..6a0cc9d5 100644 --- a/killswitch.py +++ b/killswitch.py @@ -3,7 +3,8 @@ from __future__ import annotations from copy import deepcopy from typing import ( - Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, TypedDict, Union, cast + Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, Tuple, TypedDict, + TypeVar, Union, cast ) import requests @@ -18,7 +19,7 @@ logger = EDMCLogging.get_main_logger() OLD_KILLSWITCH_URL = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/killswitches.json' DEFAULT_KILLSWITCH_URL = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/killswitches_v2.json' CURRENT_KILLSWITCH_VERSION = 2 - +UPDATABLE_DATA = Union[Mapping, Sequence] _current_version: semantic_version.Version = config.appversion_nobuild() @@ -36,7 +37,7 @@ class SingleKill(NamedTuple): """Return whether or not this SingleKill can apply rules to a dict to make it safe to use.""" return any(x is not None for x in (self.redact_fields, self.delete_fields, self.set_fields)) - def apply_rules(self, target: Dict[str, Any]): + def apply_rules(self, target: UPDATABLE_DATA) -> UPDATABLE_DATA: """ Apply the rules this SingleKill instance has to make some data okay to send. @@ -56,8 +57,10 @@ class SingleKill(NamedTuple): for key, value in (self.set_fields if self .set_fields is not None else {}).items(): _deep_apply(target, key, value) + return target -def _apply(target: Union[MutableMapping, MutableSequence], key: str, to_set: Any = None, delete: bool = False): + +def _apply(target: UPDATABLE_DATA, key: str, to_set: Any = None, delete: bool = False): """ Set or delete the given target key on the given target. @@ -74,7 +77,8 @@ def _apply(target: Union[MutableMapping, MutableSequence], key: str, to_set: Any target[key] = to_set elif isinstance(target, MutableSequence): - if (idx := _get_int(key)) is None: + idx = _get_int(key) + if idx is None: raise ValueError(f'Cannot use string {key!r} as int for index into Sequence') if delete: @@ -91,7 +95,7 @@ def _apply(target: Union[MutableMapping, MutableSequence], key: str, to_set: Any raise ValueError(f'Dont know how to apply data to {type(target)} {target!r}') -def _deep_apply(target: dict[str, Any], path: str, to_set=None, delete=False): +def _deep_apply(target: UPDATABLE_DATA, path: str, to_set=None, delete=False): """ Set the given path to the given value, if it exists. @@ -102,7 +106,7 @@ def _deep_apply(target: dict[str, Any], path: str, to_set=None, delete=False): :param to_set: the data to set, defaults to None :param delete: whether or not to delete the key rather than set it """ - current: Union[MutableMapping, MutableSequence] = target + current = target key: str = "" while '.' in path: if path in current: @@ -112,10 +116,11 @@ def _deep_apply(target: dict[str, Any], path: str, to_set=None, delete=False): key, _, path = path.partition('.') if isinstance(current, Mapping): - current = current[key] + current = current[key] # type: ignore # I really dont know at this point what you want from me mypy. elif isinstance(current, Sequence): - if (target_idx := _get_int(key)) is not None: + target_idx = _get_int(key) # mypy is broken. doesn't like := here. + if target_idx is not None: current = current[target_idx] else: raise ValueError(f'Cannot index sequence with non-int key {key!r}') @@ -207,6 +212,34 @@ class KillSwitchSet: """ return [k for k in self.kill_switches if version in k.version] + def check_killswitch(self, name: str, data: UPDATABLE_DATA, log=logger) -> Tuple[bool, UPDATABLE_DATA]: + """ + Check whether or not a killswitch is enabled. If it is, apply rules if any. + + :param name: The killswitch to check + :param data: The data to modify if needed + :return: A bool indicating if the caller should return, and either the original data or a *COPY* that has + been modified by rules + """ + res = self.get_disabled(name) + if not res.disabled: + return False, data + + log.info(f'Killswitch {name} is enabled. Checking if rules exist to make use safe') + if res.kill is None or not res.kill.has_rules: + logger.info('No rules exist. Stopping processing') + return True, data + + try: + new_data = res.kill.apply_rules(deepcopy(data)) + + except Exception as e: + log.exception(f'Exception occurred while attempting to apply rules! bailing out! {e=}') + return True, data + + log.info('Rules applied successfully, allowing execution to continue') + return False, new_data + def __str__(self) -> str: """Return a string representation of KillSwitchSet.""" return f'KillSwitchSet: {str(self.kill_switches)}' @@ -374,6 +407,11 @@ def get_disabled(id: str, *, version: semantic_version.Version = _current_versio return active.get_disabled(id, version=version) +def check_killswitch(name: str, data: UPDATABLE_DATA, log=logger) -> Tuple[bool, UPDATABLE_DATA]: + """Query the global KillSwitchSet#check_killswitch method.""" + return active.check_killswitch(name, data, log) + + def is_disabled(id: str, *, version: semantic_version.Version = _current_version) -> bool: """Query the global KillSwitchSet#is_disabled method.""" return active.is_disabled(id, version=version) From 8c0457bf473197a334680e0f528c5753bf4daddd Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 7 Jul 2021 16:28:42 +0200 Subject: [PATCH 097/316] updated inara to work with new killswitches --- plugins/inara.py | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index ff4fb55d..eded809e 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1,5 +1,6 @@ """Inara Sync.""" +import copy import json import threading import time @@ -9,7 +10,8 @@ from operator import itemgetter from threading import Lock, Thread from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, List, Mapping, NamedTuple, Optional from typing import OrderedDict as OrderedDictT -from typing import Sequence, Union, cast +from typing import Sequence, Tuple, Union, cast +from dataclasses import dataclass import requests @@ -47,7 +49,8 @@ class Credentials(NamedTuple): EVENT_DATA = Union[Mapping[str, Any], Sequence[Mapping[str, Any]]] -class Event(NamedTuple): +@dataclass +class Event: """Event represents an event for the Inara API.""" name: str @@ -334,21 +337,21 @@ def journal_entry( # noqa: C901, CCR001 :return: str - empty if no error, else error string. """ - if (ks := killswitch.get_disabled('plugins.inara.journal')).disabled: - logger.warning(f'Inara support has been disabled via killswitch: {ks.reason}') + 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 + logger.trace('returning due to killswitch match') return '' - elif (ks := killswitch.get_disabled(f'plugins.inara.journal.event.{entry["event"]}')).disabled: - logger.warning(f'event {entry["event"]} processing has been disabled via killswitch: {ks.reason}') - if ks.kill is None or 'delete_fields' not in ks.kill.additional_data: - # this can and WILL break state, but if we're concerned about it sending bad data, we'd disable globally anyway - return '' - - elif 'delete_fields' in ks.kill.additional_data: - for field in ks.kill.additional_data['delete_fields']: - entry.pop(field, None) + should_return, new_entry = killswitch.check_killswitch( + f'plugins.inara.journal.event.{entry["event"]}', entry, logger + ) + if should_return: + logger.trace('returning due to killswitch match') + # this can and WILL break state, but if we're concerned about it sending bad data, we'd disable globally anyway + return '' + entry = new_entry # type: ignore # Im done trying to teach mypy how to generic. Its the same thing, I promise. this.on_foot = state['OnFoot'] event_name: str = entry['event'] this.cmdr = cmdr @@ -1466,21 +1469,17 @@ def new_add_event( this.events[key].append(Event(name, timestamp, data)) -def clean_event_list(event_list: List[Event]) -> list[Event]: +def clean_event_list(event_list: List[Event]) -> List[Event]: """Check for killswitched events and remove or modify them as requested.""" out = [] for e in event_list: - ks = killswitch.get_disabled(f'plugins.inara.worker.{e.name}') - if not ks.disabled: - out.append(e) + bad, new_event = killswitch.check_killswitch(f'plugins.inara.worker.{e.name}', e.data, logger) + if bad: + continue - if ks.kill is not None and 'delete_fields' in ks.kill.additional_data: - # its disabled, but... we have - for field in ks.kill.additional_data['delete_fields']: - cast(Dict[str, Any], e.data).pop(field, None) - - out.append(e) + e.data = new_event + out.append(e) return out @@ -1515,6 +1514,7 @@ def new_worker(): {'eventName': e.name, 'eventTimestamp': e.timestamp, 'eventData': e.data} for e in event_list ] } + logger.info(f'sending {len(data["events"])} events for {creds.cmdr}') logger.trace_if('plugin.inara.events', f'Events:\n{json.dumps(data)}\n') From 36d786ef8c9b95d46a6a5baaa97b1c295d19c5e4 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 9 Jul 2021 13:13:31 +0200 Subject: [PATCH 098/316] Added more utility methods --- killswitch.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/killswitch.py b/killswitch.py index 6a0cc9d5..fd93cedb 100644 --- a/killswitch.py +++ b/killswitch.py @@ -3,8 +3,8 @@ from __future__ import annotations from copy import deepcopy from typing import ( - Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, Tuple, TypedDict, - TypeVar, Union, cast + TYPE_CHECKING, Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, Tuple, + TypedDict, Union, cast ) import requests @@ -172,6 +172,16 @@ class DisabledResult(NamedTuple): """Reason provided for why this killswitch exists.""" return self.kill.reason if self.kill is not None else "" + def has_kill(self) -> bool: + """Return whether or not this DisabledResult has a Kill associated with it.""" + return self.kill is not None + + def has_rules(self) -> bool: + """Return whether or not the kill on this Result contains rules.""" + # HACK: 2021-07-09 # Python/mypy/pyright does not support type guards like this yet. self.kill will always + # be non-None at the point it is evaluated + return self.has_kill() and self.kill.has_rules # type: ignore + class KillSwitchSet: """Queryable set of kill switches.""" @@ -226,10 +236,13 @@ class KillSwitchSet: return False, data log.info(f'Killswitch {name} is enabled. Checking if rules exist to make use safe') - if res.kill is None or not res.kill.has_rules: + if not res.has_rules(): logger.info('No rules exist. Stopping processing') return True, data + if TYPE_CHECKING: # pyright, mypy, please -_- + assert res.kill is not None + try: new_data = res.kill.apply_rules(deepcopy(data)) From e79975bea8584d3bb4ad9124fa97423e1cac4340 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 9 Jul 2021 13:13:58 +0200 Subject: [PATCH 099/316] Cleaned up import --- plugins/inara.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index eded809e..03939ce8 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1,17 +1,16 @@ """Inara Sync.""" -import copy import json import threading import time import tkinter as tk from collections import OrderedDict, defaultdict, deque +from dataclasses import dataclass from operator import itemgetter from threading import Lock, Thread from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, List, Mapping, NamedTuple, Optional from typing import OrderedDict as OrderedDictT -from typing import Sequence, Tuple, Union, cast -from dataclasses import dataclass +from typing import Sequence, Union, cast import requests From 98e8179c3eca40d05ca2310b0b0a56161b1d805a Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 9 Jul 2021 13:53:12 +0200 Subject: [PATCH 100/316] Dont ignore first killswitch checks changes --- plugins/inara.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 03939ce8..74520dcb 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -343,14 +343,14 @@ def journal_entry( # noqa: C901, CCR001 return '' should_return, new_entry = killswitch.check_killswitch( - f'plugins.inara.journal.event.{entry["event"]}', entry, logger + f'plugins.inara.journal.event.{entry["event"]}', new_entry, logger ) if should_return: logger.trace('returning due to killswitch match') # this can and WILL break state, but if we're concerned about it sending bad data, we'd disable globally anyway return '' - entry = new_entry # type: ignore # Im done trying to teach mypy how to generic. Its the same thing, I promise. + entry = cast(Dict[str, Any], new_entry) this.on_foot = state['OnFoot'] event_name: str = entry['event'] this.cmdr = cmdr From 78fa629fbed14b92e206be321d886e9d590d7c29 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 10 Jul 2021 15:40:46 +0200 Subject: [PATCH 101/316] updated edsm --- plugins/edsm.py | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index 2f082c2f..cf5daafd 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -15,7 +15,7 @@ import tkinter as tk from queue import Queue from threading import Thread from time import sleep -from typing import TYPE_CHECKING, Any, List, Literal, Mapping, MutableMapping, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, List, Literal, Mapping, MutableMapping, Optional, cast, Set, Tuple, Union import requests @@ -395,15 +395,19 @@ def journal_entry( # noqa: C901, CCR001 cmdr: str, is_beta: bool, system: str, station: str, entry: MutableMapping[str, Any], state: Mapping[str, Any] ) -> None: """Journal Entry hook.""" - if (ks := killswitch.get_disabled('plugins.edsm.journal')).disabled: - logger.warning(f'EDSM Journal handler disabled via killswitch: {ks.reason}') + 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.')) return - elif (ks := killswitch.get_disabled(f'plugins.edsm.journal.event.{entry["event"]}')).disabled: - logger.warning(f'Handling of event {entry["event"]} has been disabled via killswitch: {ks.reason}') + should_return, new_entry = killswitch.check_killswitch( + f'plugins.edsm.journal.event.{entry["event"]}', data=new_entry, log=logger + ) + + if should_return: return + entry = cast(MutableMapping[str, Any], new_entry) this.on_foot = state['OnFoot'] if entry['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked'): @@ -640,11 +644,16 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently retrying = 0 while retrying < 3: - if (res := killswitch.get_disabled("plugins.edsm.worker")).disabled: - logger.warning( - f'EDSM worker has been disabled via kill switch. Not uploading data. ({res.reason})' - ) + should_skip, new_item = killswitch.check_killswitch( + 'plugins.edsm.worker', item if item is not None else {}, logger + ) + + if should_skip: break + + if item is not None: + item = cast(Tuple[str, Mapping[str, Any]], new_item) + try: if item and entry['event'] not in this.discarded_events: logger.trace_if( @@ -652,6 +661,17 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently pending.append(entry) + # drop events if required by killswitch + new_pending = [] + for e in pending: + skip, new = killswitch.check_killswitch(f'plugin.edsm.worker.{e["event"]}', e, logger) + if skip: + continue + + new_pending.append(cast(Mapping[str, Any], new)) + + pending = new_pending + if pending and should_send(pending, entry['event']): logger.trace_if(CMDR_EVENTS, f'({cmdr=}, {entry["event"]=}): should_send() said True') logger.trace_if(CMDR_EVENTS, f'pending contains:\n{chr(0x0A).join(str(p) for p in pending)}') From e7e210cc878d4e05c0e0e7538db5b0a7fc01ca00 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sun, 8 Aug 2021 21:58:20 +0200 Subject: [PATCH 102/316] start on unit tests --- tests/killswitch.py/test_apply.py | 44 +++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/killswitch.py/test_apply.py diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py new file mode 100644 index 00000000..c3c97cb7 --- /dev/null +++ b/tests/killswitch.py/test_apply.py @@ -0,0 +1,44 @@ +"""Test the apply functions used by killswitch to modify data.""" +import copy +from typing import Any, Optional + +import pytest + +import killswitch +from killswitch import UPDATABLE_DATA + + +@pytest.mark.parametrize( + ("source", "key", "action", "to_set", "result"), + [ + (["this", "is", "a", "test"], "1", "delete", None, ["this", "a", "test"]), + (["this", "is", "a", "test"], "1", "", None, ["this", None, "a", "test"]), + ({"now": "with", "a": "dict"}, "now", "delete", None, {"a": "dict"}), + ({"now": "with", "a": "dict"}, "now", "", None, {"now": None, "a": "dict"}), + ] +) +def test_apply(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: + """Test that a single level apply works as expected.""" + cpy = copy.deepcopy(source) + killswitch._apply(target=cpy, key=key, to_set=to_set, delete=action == "delete") + + assert cpy == result + + +def test_apply_keyerror() -> None: + """_apply should fail when passed something that isn't a Sequence or MutableMapping.""" + with pytest.raises(ValueError, match=r'Dont know how to'): + killswitch._apply(set(), "0", None, False) # type: ignore # Its intentional that its broken + killswitch._apply(None, "", None) # type: ignore # Its intentional that its broken + + +@pytest.mark.parametrize( + ("input", "expected"), + [ + ("1", 1), ("1337", 1337), ("no.", None), ("0x10", None), ("010", 10), + (False, 0), (str((1 << 63)-1), (1 << 63)-1), (True, 1), (str(1 << 1337), 1 << 1337) + ] +) +def test_get_int(input: str, expected: Optional[int]) -> None: + """Check that _get_int doesn't throw when handed bad data.""" + assert expected == killswitch._get_int(input) From 17123907321c7eb495cea3a4bca356849fbb68f2 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Mon, 9 Aug 2021 15:31:00 +0200 Subject: [PATCH 103/316] removed old comment --- plugins/edsm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index cf5daafd..bb3370af 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -687,7 +687,7 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently f'"Location" event in pending passed should_send(),timestamp: {p["timestamp"]}' ) - creds = credentials(cmdr) # TODO: possibly unbound + creds = credentials(cmdr) if creds is None: raise ValueError("Unexpected lack of credentials") From 5b96b9b11850ab312d0ee2fabaca029d3c2e9bc4 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 10 Aug 2021 12:21:51 +0200 Subject: [PATCH 104/316] tests for _apply complete --- tests/killswitch.py/test_apply.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index c3c97cb7..bd7bdfd3 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -25,13 +25,27 @@ def test_apply(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, resul assert cpy == result -def test_apply_keyerror() -> None: +def test_apply_errors() -> None: """_apply should fail when passed something that isn't a Sequence or MutableMapping.""" with pytest.raises(ValueError, match=r'Dont know how to'): killswitch._apply(set(), "0", None, False) # type: ignore # Its intentional that its broken killswitch._apply(None, "", None) # type: ignore # Its intentional that its broken +def test_apply_no_error() -> None: + """ + _apply should not raise when deleting keys that dont exist, nor should it raise on setting keys that dont exist. + + The only exception here is for lists. if a list is malformed to what a killswitch expects, it SHOULD explode, + thus causing the killswitch to fail and eat the entire message. + """ + killswitch._apply([], "0", None, True) + killswitch._apply({}, "0", None, True) + killswitch._apply({}, "this doesn't exist", None, True) + with pytest.raises(IndexError): + killswitch._apply([], "1", "bang?") + + @pytest.mark.parametrize( ("input", "expected"), [ From da8bb27b8465acfc375769da8b3aecd5a92300a0 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 10 Aug 2021 15:23:55 +0200 Subject: [PATCH 105/316] added deep get tests --- tests/killswitch.py/test_apply.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index bd7bdfd3..21f8c2cc 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -56,3 +56,23 @@ def test_apply_no_error() -> None: def test_get_int(input: str, expected: Optional[int]) -> None: """Check that _get_int doesn't throw when handed bad data.""" assert expected == killswitch._get_int(input) + + +@pytest.mark.parametrize( + ("source", "key", "action", "to_set", "result"), + [ + (["this", "is", "a", "test"], "1", "delete", None, ["this", "a", "test"]), + (["this", "is", "a", "test"], "1", "", None, ["this", None, "a", "test"]), + ({"now": "with", "a": "dict"}, "now", "delete", None, {"a": "dict"}), + ({"now": "with", "a": "dict"}, "now", "", None, {"now": None, "a": "dict"}), + ({"depth": {"is": "important"}}, "depth.is", "", "nonexistent", {"depth": {"is": "nonexistent"}}), + ([{"test": ["stuff"]}], "0.test.0", "", "things", [{"test": ["things"]}]), + (({"test": {"with": ["a", "tuple"]}},), "0.test.with.0", "delete", "", ({"test": {"with": ["tuple"]}},)), + ({"test": ["with a", {"set", "of", "stuff"}]}, "test.1", "delete", "", {"test": ["with a"]}) + ], +) +def test_deep_get(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: + """Test _deep_get behaves as expected.""" + cpy = copy.deepcopy(source) + killswitch._deep_apply(target=cpy, path=key, to_set=to_set, delete=action == "delete") + assert cpy == result From df200d2c22bffb1a0d2b75173405bdb8944ee2da Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 10 Aug 2021 15:55:25 +0200 Subject: [PATCH 106/316] quoting fixes --- tests/killswitch.py/test_apply.py | 50 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index 21f8c2cc..5033163e 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -9,18 +9,19 @@ from killswitch import UPDATABLE_DATA @pytest.mark.parametrize( - ("source", "key", "action", "to_set", "result"), + ('source', 'key', 'action', 'to_set', 'result'), [ - (["this", "is", "a", "test"], "1", "delete", None, ["this", "a", "test"]), - (["this", "is", "a", "test"], "1", "", None, ["this", None, "a", "test"]), - ({"now": "with", "a": "dict"}, "now", "delete", None, {"a": "dict"}), - ({"now": "with", "a": "dict"}, "now", "", None, {"now": None, "a": "dict"}), + (['this', 'is', 'a', 'test'], '1', 'delete', None, ['this', 'a', 'test']), + (['this', 'is', 'a', 'test'], '1', '', None, ['this', None, 'a', 'test']), + ({'now': 'with', 'a': 'dict'}, 'now', 'delete', None, {'a': 'dict'}), + ({'now': 'with', 'a': 'dict'}, 'now', '', None, {'now': None, 'a': 'dict'}), + (['test append'], '1', '', 'yay', ['test append', 'yay']) ] ) def test_apply(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: """Test that a single level apply works as expected.""" cpy = copy.deepcopy(source) - killswitch._apply(target=cpy, key=key, to_set=to_set, delete=action == "delete") + killswitch._apply(target=cpy, key=key, to_set=to_set, delete=action == 'delete') assert cpy == result @@ -28,8 +29,11 @@ def test_apply(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, resul def test_apply_errors() -> None: """_apply should fail when passed something that isn't a Sequence or MutableMapping.""" with pytest.raises(ValueError, match=r'Dont know how to'): - killswitch._apply(set(), "0", None, False) # type: ignore # Its intentional that its broken - killswitch._apply(None, "", None) # type: ignore # Its intentional that its broken + killswitch._apply(set(), '0', None, False) # type: ignore # Its intentional that its broken + killswitch._apply(None, '', None) # type: ignore # Its intentional that its broken + + with pytest.raises(ValueError, match=r'Cannot use string'): + killswitch._apply([], 'test', None, False) def test_apply_no_error() -> None: @@ -39,17 +43,17 @@ def test_apply_no_error() -> None: The only exception here is for lists. if a list is malformed to what a killswitch expects, it SHOULD explode, thus causing the killswitch to fail and eat the entire message. """ - killswitch._apply([], "0", None, True) - killswitch._apply({}, "0", None, True) + killswitch._apply([], '0', None, True) + killswitch._apply({}, '0', None, True) killswitch._apply({}, "this doesn't exist", None, True) with pytest.raises(IndexError): - killswitch._apply([], "1", "bang?") + killswitch._apply([], '1', 'bang?') @pytest.mark.parametrize( - ("input", "expected"), + ('input', 'expected'), [ - ("1", 1), ("1337", 1337), ("no.", None), ("0x10", None), ("010", 10), + ('1', 1), ('1337', 1337), ('no.', None), ('0x10', None), ('010', 10), (False, 0), (str((1 << 63)-1), (1 << 63)-1), (True, 1), (str(1 << 1337), 1 << 1337) ] ) @@ -59,20 +63,20 @@ def test_get_int(input: str, expected: Optional[int]) -> None: @pytest.mark.parametrize( - ("source", "key", "action", "to_set", "result"), + ('source', 'key', 'action', 'to_set', 'result'), [ - (["this", "is", "a", "test"], "1", "delete", None, ["this", "a", "test"]), - (["this", "is", "a", "test"], "1", "", None, ["this", None, "a", "test"]), - ({"now": "with", "a": "dict"}, "now", "delete", None, {"a": "dict"}), - ({"now": "with", "a": "dict"}, "now", "", None, {"now": None, "a": "dict"}), - ({"depth": {"is": "important"}}, "depth.is", "", "nonexistent", {"depth": {"is": "nonexistent"}}), - ([{"test": ["stuff"]}], "0.test.0", "", "things", [{"test": ["things"]}]), - (({"test": {"with": ["a", "tuple"]}},), "0.test.with.0", "delete", "", ({"test": {"with": ["tuple"]}},)), - ({"test": ["with a", {"set", "of", "stuff"}]}, "test.1", "delete", "", {"test": ["with a"]}) + (['this', 'is', 'a', 'test'], '1', 'delete', None, ['this', 'a', 'test']), + (['this', 'is', 'a', 'test'], '1', '', None, ['this', None, 'a', 'test']), + ({'now': 'with', 'a': 'dict'}, 'now', 'delete', None, {'a': 'dict'}), + ({'now': 'with', 'a': 'dict'}, 'now', '', None, {'now': None, 'a': 'dict'}), + ({'depth': {'is': 'important'}}, 'depth.is', '', 'nonexistent', {'depth': {'is': 'nonexistent'}}), + ([{'test': ['stuff']}], '0.test.0', '', 'things', [{'test': ['things']}]), + (({'test': {'with': ['a', 'tuple']}},), '0.test.with.0', 'delete', '', ({'test': {'with': ['tuple']}},)), + ({'test': ['with a', {'set', 'of', 'stuff'}]}, 'test.1', 'delete', '', {'test': ['with a']}) ], ) def test_deep_get(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: """Test _deep_get behaves as expected.""" cpy = copy.deepcopy(source) - killswitch._deep_apply(target=cpy, path=key, to_set=to_set, delete=action == "delete") + killswitch._deep_apply(target=cpy, path=key, to_set=to_set, delete=action == 'delete') assert cpy == result From 9255c4fa9601d556b1011c98b8ef85a933302e46 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 10 Aug 2021 16:18:23 +0200 Subject: [PATCH 107/316] resolved edgecase with dotted keys --- killswitch.py | 20 +++++++++++++++++++- tests/killswitch.py/test_apply.py | 5 ++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/killswitch.py b/killswitch.py index fd93cedb..499b71e2 100644 --- a/killswitch.py +++ b/killswitch.py @@ -113,7 +113,25 @@ def _deep_apply(target: UPDATABLE_DATA, path: str, to_set=None, delete=False): # it exists on this level, dont go further break - key, _, path = path.partition('.') + elif isinstance(current, Mapping) and any('.' in k and path.startswith(k) for k in current.keys()): + # there is a dotted key in here that can be used for this + # if theres a dotted key in here (must be a mapping), use that if we can + + keys = current.keys() + for k in filter(lambda x: '.' in x, keys): + if path.startswith(k): + key = k + path = path.removeprefix(k) + # we assume that the `.` here is for "accessing" the next key. + if path[0] == '.': + path = path[1:] + + if len(path) == 0: + path = key + break + + else: + key, _, path = path.partition('.') if isinstance(current, Mapping): current = current[key] # type: ignore # I really dont know at this point what you want from me mypy. diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index 5033163e..2bbb20c5 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -72,7 +72,10 @@ def test_get_int(input: str, expected: Optional[int]) -> None: ({'depth': {'is': 'important'}}, 'depth.is', '', 'nonexistent', {'depth': {'is': 'nonexistent'}}), ([{'test': ['stuff']}], '0.test.0', '', 'things', [{'test': ['things']}]), (({'test': {'with': ['a', 'tuple']}},), '0.test.with.0', 'delete', '', ({'test': {'with': ['tuple']}},)), - ({'test': ['with a', {'set', 'of', 'stuff'}]}, 'test.1', 'delete', '', {'test': ['with a']}) + ({'test': ['with a', {'set', 'of', 'stuff'}]}, 'test.1', 'delete', '', {'test': ['with a']}), + ({'keys.can.have.': 'dots!'}, 'keys.can.have.', '', '.s!', {'keys.can.have.': '.s!'}), + ({'multilevel.keys': {'with.dots': False}}, 'multilevel.keys.with.dots', + '', True, {'multilevel.keys': {'with.dots': True}}) ], ) def test_deep_get(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: From 3da89e838f2a8361e3194d83b703893a24ad7fcf Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 10 Aug 2021 16:23:26 +0200 Subject: [PATCH 108/316] finised deep_get tests --- tests/killswitch.py/test_apply.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index 2bbb20c5..0e10849d 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -75,7 +75,10 @@ def test_get_int(input: str, expected: Optional[int]) -> None: ({'test': ['with a', {'set', 'of', 'stuff'}]}, 'test.1', 'delete', '', {'test': ['with a']}), ({'keys.can.have.': 'dots!'}, 'keys.can.have.', '', '.s!', {'keys.can.have.': '.s!'}), ({'multilevel.keys': {'with.dots': False}}, 'multilevel.keys.with.dots', - '', True, {'multilevel.keys': {'with.dots': True}}) + '', True, {'multilevel.keys': {'with.dots': True}}), + ({'dotted.key.one.level': False}, 'dotted.key.one.level', '', True, {'dotted.key.one.level': True}), + ({'dotted.key.one.level': {"oops.i.lied": False}}, 'dotted.key.one.level.oops.i.lied', + '', True, {'dotted.key.one.level': {"oops.i.lied": True}}), ], ) def test_deep_get(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: From baa70ff93807ee997cbea5fc1878492ec763cc28 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 01:10:14 +0200 Subject: [PATCH 109/316] Added defaults to SingleKill --- killswitch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/killswitch.py b/killswitch.py index 499b71e2..f4a5f292 100644 --- a/killswitch.py +++ b/killswitch.py @@ -28,9 +28,9 @@ class SingleKill(NamedTuple): match: str reason: str - redact_fields: Optional[List[str]] - delete_fields: Optional[List[str]] - set_fields: Optional[Dict[str, Any]] + redact_fields: Optional[List[str]] = None + delete_fields: Optional[List[str]] = None + set_fields: Optional[Dict[str, Any]] = None @property def has_rules(self) -> bool: From b04825985b9148d4382b13958ef69f341fdf2728 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 02:16:53 +0200 Subject: [PATCH 110/316] added version optional arg --- killswitch.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/killswitch.py b/killswitch.py index f4a5f292..160231de 100644 --- a/killswitch.py +++ b/killswitch.py @@ -215,6 +215,10 @@ class KillSwitchSet: :param version: The version to check killswitches for, defaults to the current EDMC version :return: a namedtuple indicating status and reason, if any """ + + if isinstance(version, str): + version = semantic_version.Version.coerce(version) + for ks in self.kill_switches: if version not in ks.version: continue @@ -240,7 +244,7 @@ class KillSwitchSet: """ return [k for k in self.kill_switches if version in k.version] - def check_killswitch(self, name: str, data: UPDATABLE_DATA, log=logger) -> Tuple[bool, UPDATABLE_DATA]: + def check_killswitch(self, name: str, data: UPDATABLE_DATA, log=logger, version=_current_version) -> Tuple[bool, UPDATABLE_DATA]: """ Check whether or not a killswitch is enabled. If it is, apply rules if any. @@ -249,7 +253,7 @@ class KillSwitchSet: :return: A bool indicating if the caller should return, and either the original data or a *COPY* that has been modified by rules """ - res = self.get_disabled(name) + res = self.get_disabled(name, version=version) if not res.disabled: return False, data From 3e297af79cfa2a6f5de3fa89540f2e4450eff218 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 02:17:13 +0200 Subject: [PATCH 111/316] added killswitch tests --- tests/killswitch.py/test_killswitch.py | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/killswitch.py/test_killswitch.py diff --git a/tests/killswitch.py/test_killswitch.py b/tests/killswitch.py/test_killswitch.py new file mode 100644 index 00000000..dc53941e --- /dev/null +++ b/tests/killswitch.py/test_killswitch.py @@ -0,0 +1,49 @@ +"""Tests of killswitch behaviour.""" +from typing import Optional + +import pytest +import semantic_version + +import killswitch + +TEST_SET = killswitch.KillSwitchSet([ + killswitch.KillSwitches( + version=semantic_version.SimpleSpec('1.0.0'), + kills={ + 'no-actions': killswitch.SingleKill('no-actions', 'test'), + 'delete-action': killswitch.SingleKill('delete-action', 'remove stuff', delete_fields=['a', 'b.c']), + 'delete-action-l': killswitch.SingleKill('delete-action-l', 'remove stuff', delete_fields=['2', '0']), + 'set-action': killswitch.SingleKill('set-action', 'set stuff', set_fields={'a': False, 'b.c': True}), + 'redact-action': killswitch.SingleKill('redact-action', 'redact stuff', redact_fields=['a', 'b.c']) + } + ) +]) + + +@pytest.mark.parametrize( + ('input', 'kill', 'should_pass', 'result', 'version'), + [ + ([], 'doesnt-exist', True, None, '1.0.0'), + # should fail, attempts to use 'a' to index a list + (['a', 'b', 'c'], 'delete-action', False, ['a', 'b', 'c'], '1.0.0'), + (['a', 'b', 'c'], 'delete-action-l', True, ['b'], '1.0.0'), + (set(), 'delete-action-l', False, None, '1.0.0'), # set should be thrown out because it cant be indext + (['a', 'b'], 'delete-action-l', True, ['b'], '1.0.0'), # has a missing value, but that's fine for delete + (['a', 'b'], 'delete-action-l', True, ['a', 'b'], '1.1.0'), # wrong version + ], +) +def test_killswitch( + input: killswitch.UPDATABLE_DATA, kill: str, should_pass: bool, result: Optional[killswitch.UPDATABLE_DATA], + version: str +) -> None: + """Simple killswitch tests.""" + should_return, res = TEST_SET.check_killswitch(kill, input, version=version) + + assert (not should_return) == should_pass, ( + f'expected to {"pass" if should_pass else "fail"}, but {"passed" if not should_pass else "failed"}' + ) + + if result is None: + return # we didn't expect any result + + assert res == result From e825902d13bd91bbef99331088195834ee054dc3 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 13:16:40 +0200 Subject: [PATCH 112/316] fixed typo --- docs/Releasing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Releasing.md b/docs/Releasing.md index 1ef33faa..7c86384f 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -235,7 +235,7 @@ the .exe and .msi because ChangeLog.md is bundled with the install. If anything in this new release addresses a bug that causes, e.g. bad data to be sent over EDDN, then you should add an appropriate entry to the killswitches.json file *in the `releases` branch*. That file **must only ever -be commited to the `releases` branch!!!** See [docs/Killswitches.md](Killswitches.md). +be committed to the `releases` branch!!!** See [docs/Killswitches.md](Killswitches.md). # Packaging & Installer Generation From 78c807a6cc2c7e58b08b3bbb023ccb11f7463697 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 13:31:56 +0200 Subject: [PATCH 113/316] Clarified docs --- docs/Killswitches.md | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 91fbb26e..40d1f747 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -21,6 +21,7 @@ The `kill_switches` array contains kill switch objects. Each contains the follow Each entry in `kills` must contain at least a `reason` field describing why the killswitch was added. EDMC will show this to the user (for internal killswitches, anyway). + | Key (* = required) | Type | Description | | -----------------: | :--------------: | :-------------------------------------------------------------------------------------------- | | `reason`* | `str` | The reason that this killswitch was added | @@ -46,13 +47,18 @@ An example follows: "ruler_map": {"emperor": "scimitar"} }, "redact_fields": ["relation_to_thargoids"] - } + }, + "plugins.some_plugin.some_thing": {} } } ] } ``` +- `plugins.edsm.send` will have fields deleted, set, and redacted, and then will *not* be halted, the send will continue with the modified data. +- `plugins.some_plugin.some_thing` will never be allowed to continue (as all fields are blank) + + ### Versions Versions are checked using contains checks on `semantic_version.SimpleSpec` instances. @@ -63,19 +69,23 @@ Plugins may use the killswitch system simply by hosting their own version of the using `killswitch.get_kill_switches(target='https://example.com/myplugin_killswitches.json')`. The returned object can be used to query the kill switch set, see the docstrings for more information on specifying versions. -The version of the JSON file will be automatically upgraded if possible by the code KillSwitch code. +The version of the JSON file will be automatically upgraded if possible by the code KillSwitch code. No behaviour changes will occur--Any killswitches defined in older + versions will simply become unconditional kills in the new version. ## Currently supported killswitch strings - <!-- TODO: update this with new behaviour for various fields --> The current recognised (to EDMC and its internal plugins) killswitch strings are as follows: -| Kill Switch | Description | -| :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- | -| `plugins.eddn.send` | Disables all use of the send method on EDDN (effectively disables EDDN updates) | -| `plugins.(eddn\|inara\|edsm\|eddb).journal` | Disables all journal processing for EDDN/EDSM/INARA | -| `plugins.(edsm\|inara).worker` | Disables the EDSM/INARA worker thread (effectively disables updates) (does not close thread) | -| `plugins.(edsm\|inara).worker.$eventname` | Disables the EDSM/INARA worker for the given eventname, OR if delete_fields exists, removes the fields from the event | -| `plugins.(eddn\|inara\|edsm).journal.event.$eventname` | Specific events to disable processing for. OR, if delete_fields exists as additional_data, the fields listed will be removed before processing | + +| Kill Switch | Supported Plugins | Description | +| :------------------------------------------- | :---------------------: | :---------------------------------------------------------------------------------------- | +| *`plugins.eddn.send` | eddn | Disables all use of the send method on EDDN (effectively disables EDDN updates) | +| `plugins.<plugin>.journal` | eddn, inara, edsm, eddb | Disables all journal processing for the plugin | +| `plugins.<plugin>.worker` | edsm, inara | Disables the plugins worker thread (effectively disables updates) (does not close thread) | +| `plugins.<plugin>.worker.<eventname>` | edsm, inara | Disables the plugin worker for the given eventname | +| `plugins.<plugin>.journal.event.<eventname>` | eddn, inara, edsm | Specific events to disable processing for. | + +Killswitches marked with `*` do **not** support modification of their values via set/redact/delete. And as such any match +will simply stop processing. ## File location From 0c34e363f35ad18be84e0b0e3b080cf5d407c4ad Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 13:40:38 +0200 Subject: [PATCH 114/316] fix flake8 complaint --- killswitch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/killswitch.py b/killswitch.py index 160231de..e3f89ed2 100644 --- a/killswitch.py +++ b/killswitch.py @@ -244,7 +244,9 @@ class KillSwitchSet: """ return [k for k in self.kill_switches if version in k.version] - def check_killswitch(self, name: str, data: UPDATABLE_DATA, log=logger, version=_current_version) -> Tuple[bool, UPDATABLE_DATA]: + def check_killswitch( + self, name: str, data: UPDATABLE_DATA, log=logger, version=_current_version + ) -> Tuple[bool, UPDATABLE_DATA]: """ Check whether or not a killswitch is enabled. If it is, apply rules if any. From a37b1882db6c62a595ef9324dbfaa044c4f4775a Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 13:42:08 +0200 Subject: [PATCH 115/316] fixed import order --- plugins/edsm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index bb3370af..e8e3c81d 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -15,7 +15,7 @@ import tkinter as tk from queue import Queue from threading import Thread from time import sleep -from typing import TYPE_CHECKING, Any, List, Literal, Mapping, MutableMapping, Optional, cast, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, List, Literal, Mapping, MutableMapping, Optional, Set, Tuple, Union, cast import requests From 1c3c29abb621c122bba4fc91e1fbfc3ee6daa612 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 13:48:09 +0200 Subject: [PATCH 116/316] silcenced flake8 --- killswitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/killswitch.py b/killswitch.py index e3f89ed2..3a17f022 100644 --- a/killswitch.py +++ b/killswitch.py @@ -95,7 +95,7 @@ def _apply(target: UPDATABLE_DATA, key: str, to_set: Any = None, delete: bool = raise ValueError(f'Dont know how to apply data to {type(target)} {target!r}') -def _deep_apply(target: UPDATABLE_DATA, path: str, to_set=None, delete=False): +def _deep_apply(target: UPDATABLE_DATA, path: str, to_set=None, delete=False): # noqa: CCR001 # Recursive silliness. """ Set the given path to the given value, if it exists. From 3133cba0a472e0c3054608ec82f10cf6771c1b58 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 17:06:01 +0200 Subject: [PATCH 117/316] checked negatives, added test to that effect --- killswitch.py | 5 +++-- tests/killswitch.py/test_apply.py | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/killswitch.py b/killswitch.py index 3a17f022..346d4099 100644 --- a/killswitch.py +++ b/killswitch.py @@ -81,8 +81,9 @@ def _apply(target: UPDATABLE_DATA, key: str, to_set: Any = None, delete: bool = if idx is None: raise ValueError(f'Cannot use string {key!r} as int for index into Sequence') - if delete: - if len(target) > idx: + if delete and len(target) > 0: + length = len(target) + if (idx > 0 and length > idx) or idx >= -length: target.pop(idx) elif len(target) == idx: diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index 0e10849d..29517203 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -15,7 +15,10 @@ from killswitch import UPDATABLE_DATA (['this', 'is', 'a', 'test'], '1', '', None, ['this', None, 'a', 'test']), ({'now': 'with', 'a': 'dict'}, 'now', 'delete', None, {'a': 'dict'}), ({'now': 'with', 'a': 'dict'}, 'now', '', None, {'now': None, 'a': 'dict'}), - (['test append'], '1', '', 'yay', ['test append', 'yay']) + (['test append'], '1', '', 'yay', ['test append', 'yay']), + (['test neg del'], '-1', 'delete', None, []), + (['test neg del'], '-1337', 'delete', None, ['test neg del']), + (['test neg del'], '-2', 'delete', None, ['test neg del']), ] ) def test_apply(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: From 532716e748d7c029301224e0102ee0b913100103 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Wed, 11 Aug 2021 17:38:50 +0200 Subject: [PATCH 118/316] use range instead of silly checks --- killswitch.py | 2 +- tests/killswitch.py/test_apply.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/killswitch.py b/killswitch.py index 346d4099..d6e7b4a9 100644 --- a/killswitch.py +++ b/killswitch.py @@ -83,7 +83,7 @@ def _apply(target: UPDATABLE_DATA, key: str, to_set: Any = None, delete: bool = if delete and len(target) > 0: length = len(target) - if (idx > 0 and length > idx) or idx >= -length: + if idx in range(-length, length): target.pop(idx) elif len(target) == idx: diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index 29517203..a99c55e0 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -19,6 +19,7 @@ from killswitch import UPDATABLE_DATA (['test neg del'], '-1', 'delete', None, []), (['test neg del'], '-1337', 'delete', None, ['test neg del']), (['test neg del'], '-2', 'delete', None, ['test neg del']), + (['test too high del'], '30', 'delete', None, ['test too high del']), ] ) def test_apply(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: From 5f970217e7cb2cb380e50e1ec22f66d26a1536fe Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 14 Aug 2021 13:16:40 +0200 Subject: [PATCH 119/316] added verification script for killswitches --- scripts/killswitch_test.py | 125 +++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 scripts/killswitch_test.py diff --git a/scripts/killswitch_test.py b/scripts/killswitch_test.py new file mode 100644 index 00000000..314e3344 --- /dev/null +++ b/scripts/killswitch_test.py @@ -0,0 +1,125 @@ +"""Print information about killswitch json files.""" +import json +import sys + +# Yes this is gross. No I cant fix it. EDMC doesn't use python modules currently and changing that would be messy. +sys.path.append('.') +from killswitch import KillSwitchSet, SingleKill, parse_kill_switches # noqa: E402 + +KNOWN_KILLSWITCH_NAMES: list[str] = [ + # edsm + 'plugins.edsm.worker', + 'plugins.edsm.worker.$event', + 'plugins.edsm.journal', + 'plugins.edsm.journal.event.$event', + + # inara + 'plugins.inara.journal', + 'plugins.inara.journal.event.$event', + 'plugins.inara.worker', + 'plugins.inara.worker.$event', + + # eddn + 'plugins.eddn.send', + 'plugins.eddn.journal', + 'plugins.eddn.journal.event.$event', + + # eddb + 'plugins.eddb.journal', + 'plugins.eddb.journal.event.$event' +] + +SPLIT_KNOWN_NAMES = list(map(lambda x: x.split('.'), KNOWN_KILLSWITCH_NAMES)) + + +def match_exists(match: str) -> tuple[bool, str]: + """Check that a match matching the above defined known list exists.""" + split_match = match.split('.') + highest_match = 0 + closest = [] + + for known_split in SPLIT_KNOWN_NAMES: + if len(known_split) != len(split_match): + continue # couldn't possibly match this + + if known_split == split_match: + return True, "" + + matched_fields = sum(len(k) for k, s in zip(known_split, split_match) if k == s or k[0] == '$') + if highest_match < matched_fields: + matched_fields = highest_match + closest = list(known_split) + + if matched_fields == len(known_split): + return True, "" + + return False, ".".join(closest) + + +def show_killswitch_set_info(ks: KillSwitchSet) -> None: + """Show information about the given KillSwitchSet.""" + for kill_version in ks.kill_switches: + print(f'Kills matching version mask {kill_version.version}') + for kill in kill_version.kills.values(): + print_singlekill_info(kill) + + +def print_singlekill_info(s: SingleKill): + """Print info about a single SingleKill instance.""" + ok, closest_match = match_exists(s.match) + if ok: + print(f'\t- {s.match}') + + else: + print( + f'\t- {s.match} -- Does not match existing killswitches! ' + f'Typo or out of date script? (closest: {closest_match!r})' + ) + + print(f'\t\tReason specified is: {s.reason!r}') + print() + + if not s.has_rules: + print(f'\t\tDoes not set, redact, or delete fields. This will always stop execution for {s.match}') + return + + print(f'\t\tThe folowing changes are required for {s.match} execution to continue') + if s.set_fields: + max_field_len = max(len(f) for f in s.set_fields) + 3 + print(f'\t\tSets {len(s.set_fields)} fields:') + for f, c in s.set_fields.items(): + print(f'\t\t\t- {f.ljust(max_field_len)} -> {c}') + + print() + + if s.redact_fields: + max_field_len = max(len(f) for f in s.redact_fields) + 3 + print(f'\t\tRedacts {len(s.redact_fields)} fields:') + for f in s.redact_fields: + print(f'\t\t\t- {f.ljust(max_field_len)} -> "REDACTED"') + + print() + + if s.delete_fields: + print(f'\t\tDeletes {len(s.delete_fields)} fields:') + for f in s.delete_fields: + print(f'\t\t\t- {f}') + + print() + + +if __name__ == '__main__': + if len(sys.argv) == 1: + print("killswitch_test.py [file or - for stdin]") + sys.exit(1) + + file_name = sys.argv[1] + if file_name == '-': + file = sys.stdin + else: + file = open(file_name) + + res = json.load(file) + file.close() + + show_killswitch_set_info(KillSwitchSet(parse_kill_switches(res))) From 581fa05143da62bae2c5f6b1af0e3e70163e0052 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 14 Aug 2021 13:16:56 +0200 Subject: [PATCH 120/316] fixed incorrect example --- docs/Killswitches.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 40d1f747..bf66daa2 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -48,7 +48,9 @@ An example follows: }, "redact_fields": ["relation_to_thargoids"] }, - "plugins.some_plugin.some_thing": {} + "plugins.some_plugin.some_thing": { + "reason": "Some thing is disabled pending investigation of NMLA relations." + } } } ] From 710807f5853646cbb1e689b087e89689e8ad5a48 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 14 Aug 2021 13:41:01 +0200 Subject: [PATCH 121/316] made precedence of operators a part of the API --- docs/Killswitches.md | 5 ++++- killswitch.py | 8 +++----- tests/killswitch.py/test_killswitch.py | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index bf66daa2..bfa71e74 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -25,9 +25,12 @@ this to the user (for internal killswitches, anyway). | Key (* = required) | Type | Description | | -----------------: | :--------------: | :-------------------------------------------------------------------------------------------- | | `reason`* | `str` | The reason that this killswitch was added | -| `delete_fields` | `List[str]` | A list of fields in the matching event to be removed, if they exist. | | `set_fields` | `Dict[str, Any]` | A map of key -> contents to update (or overwrite) existing data with | | `redact_fields` | `List[str]` | A list of fields to redact. This is equivalent to setting the fields to the string "REDACTED" | +| `delete_fields` | `List[str]` | A list of fields in the matching event to be removed, if they exist. | + +The order listed above is the precedence for actions. i.e. All set fields are set, then all redact fields are redacted +then all delete fields are deleted. An example follows: diff --git a/killswitch.py b/killswitch.py index d6e7b4a9..728a4350 100644 --- a/killswitch.py +++ b/killswitch.py @@ -45,8 +45,9 @@ class SingleKill(NamedTuple): :param target: data to apply a rule to """ - # fields that contain . might be going deeper into the dict. check here FIRST to see if they exist at the top - # level, if not, then work our way down + + for key, value in (self.set_fields if self .set_fields is not None else {}).items(): + _deep_apply(target, key, value) for key in (self.redact_fields if self.redact_fields is not None else []): _deep_apply(target, key, "REDACTED") @@ -54,9 +55,6 @@ class SingleKill(NamedTuple): for key in (self.delete_fields if self.delete_fields is not None else []): _deep_apply(target, key, delete=True) - for key, value in (self.set_fields if self .set_fields is not None else {}).items(): - _deep_apply(target, key, value) - return target diff --git a/tests/killswitch.py/test_killswitch.py b/tests/killswitch.py/test_killswitch.py index dc53941e..ebbae1b1 100644 --- a/tests/killswitch.py/test_killswitch.py +++ b/tests/killswitch.py/test_killswitch.py @@ -1,4 +1,5 @@ """Tests of killswitch behaviour.""" +import copy from typing import Optional import pytest @@ -47,3 +48,28 @@ def test_killswitch( return # we didn't expect any result assert res == result + + +@pytest.mark.parametrize( + ('kill_dict', 'input', 'result'), + [ + ({'set_fields': {'test': None}}, {}, {'test': None}), + ({'set_fields': {'test': None}, 'delete_fields': ['test']}, {}, {}), + ({'set_fields': {'test': None}, 'redact_fields': ['test']}, {}, {'test': 'REDACTED'}), + ({'set_fields': {'test': None}, 'redact_fields': ['test'], 'delete_fields': ['test']}, {}, {}), + + ], +) +def test_operator_precedence( + kill_dict: killswitch.SingleKillSwitchJSON, input: killswitch.UPDATABLE_DATA, result: killswitch.UPDATABLE_DATA +) -> None: + """Ensure that operators are being applied in the correct order.""" + kill = killswitch.SingleKill( + "", "", kill_dict.get('redact_fields'), kill_dict.get('delete_fields'), kill_dict.get('set_fields') + ) + + cpy = copy.deepcopy(input) + + kill.apply_rules(cpy) + + assert cpy == result From e081e1991b4029f0fc95b78be2498cb312367e1c Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 14 Aug 2021 13:43:14 +0200 Subject: [PATCH 122/316] docstring fixups --- killswitch.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/killswitch.py b/killswitch.py index 728a4350..b63bb5ea 100644 --- a/killswitch.py +++ b/killswitch.py @@ -45,7 +45,6 @@ class SingleKill(NamedTuple): :param target: data to apply a rule to """ - for key, value in (self.set_fields if self .set_fields is not None else {}).items(): _deep_apply(target, key, value) @@ -98,8 +97,8 @@ def _deep_apply(target: UPDATABLE_DATA, path: str, to_set=None, delete=False): """ Set the given path to the given value, if it exists. - if the path has dots (ascii period -- '.'), it will be successively split if possible for deeper indices into - target + if the path has dots (ascii period -- '.'), it will be successively split + if possible for deeper indices into target :param target: the dict to modify :param to_set: the data to set, defaults to None @@ -211,7 +210,8 @@ class KillSwitchSet: Return whether or not the given feature ID is disabled by a killswitch for the given version. :param id: The feature ID to check - :param version: The version to check killswitches for, defaults to the current EDMC version + :param version: The version to check killswitches for, defaults to the + current EDMC version :return: a namedtuple indicating status and reason, if any """ @@ -251,8 +251,8 @@ class KillSwitchSet: :param name: The killswitch to check :param data: The data to modify if needed - :return: A bool indicating if the caller should return, and either the original data or a *COPY* that has - been modified by rules + :return: A bool indicating if the caller should return, and either the + original data or a *COPY* that has been modified by rules """ res = self.get_disabled(name, version=version) if not res.disabled: From 665548fde307afc8ff6fd367a9a64e153bdfca96 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 14 Aug 2021 13:47:56 +0200 Subject: [PATCH 123/316] Clarified that SimpleSpec supports both version wildcards and ranges --- docs/Killswitches.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index bfa71e74..41935318 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -14,10 +14,10 @@ Killswitches are stored in a JSON file that is queried by EDMC on startup. The f The `kill_switches` array contains kill switch objects. Each contains the following fields: -| Key | Type | Description | -| --------: | :--------------------: | :--------------------------------------------------------------------------- | -| `version` | `version spec` | The version of EDMC these kill switches apply to (Must be valid semver spec) | -| `kills` | `Dict[str, Dict[...]]` | The various keys disabled -> definition of the killswitch behaviour | +| Key | Type | Description | +| --------: | :---------------------------: | :--------------------------------------------------------------------------- | +| `version` | `version spec (see Versions)` | The version of EDMC these kill switches apply to (Must be valid semver spec) | +| `kills` | `Dict[str, Dict[...]]` | The various keys disabled -> definition of the killswitch behaviour | Each entry in `kills` must contain at least a `reason` field describing why the killswitch was added. EDMC will show this to the user (for internal killswitches, anyway). @@ -66,7 +66,9 @@ An example follows: ### Versions -Versions are checked using contains checks on `semantic_version.SimpleSpec` instances. +Versions are checked using contains checks on `semantic_version.SimpleSpec` instances. SimpleSpec supports both specific +versions (`1.2.3`), non-specific ranges (`1.0` will match `1.0.1` and `1.0.5` etc), wildcards (`1.2.*`), +and ranges (`<1.0.0`, `>=2.0.0`) ## Plugin support From 984d09947c3ebf0c8a9a54274530cf136360b988 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sat, 14 Aug 2021 13:57:32 +0200 Subject: [PATCH 124/316] added threaded request helper --- docs/Killswitches.md | 11 +++++++++-- killswitch.py | 21 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 41935318..85275819 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -76,8 +76,15 @@ Plugins may use the killswitch system simply by hosting their own version of the using `killswitch.get_kill_switches(target='https://example.com/myplugin_killswitches.json')`. The returned object can be used to query the kill switch set, see the docstrings for more information on specifying versions. -The version of the JSON file will be automatically upgraded if possible by the code KillSwitch code. No behaviour changes will occur--Any killswitches defined in older - versions will simply become unconditional kills in the new version. +A helper method `killswitch.get_kill_switch_thread` is provided to allow for simple nonblocking requests for +KillSwitches. It starts a new thread, performs the HTTP request, and sends the results to the given callback. + +**Note that your callback is invoked off-thread. Take precaution for locking if required, and do _NOT_ use tkinter** +**methods** + +The version of the JSON file will be automatically upgraded if possible by the code KillSwitch code. +No behaviour changes will occur--Any killswitches defined in older versions will simply become unconditional kills in +the new version. ## Currently supported killswitch strings diff --git a/killswitch.py b/killswitch.py index b63bb5ea..c89d7ace 100644 --- a/killswitch.py +++ b/killswitch.py @@ -1,10 +1,11 @@ """Fetch kill switches from EDMC Repo.""" from __future__ import annotations +import threading from copy import deepcopy from typing import ( - TYPE_CHECKING, Any, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, Tuple, - TypedDict, Union, cast + TYPE_CHECKING, Any, Callable, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, + Tuple, TypedDict, Union, cast ) import requests @@ -414,6 +415,22 @@ def get_kill_switches(target=DEFAULT_KILLSWITCH_URL, fallback: Optional[str] = N return KillSwitchSet(parse_kill_switches(data)) +def get_kill_switches_thread( + target, callback: Callable[[Optional[KillSwitchSet]], None], fallback: Optional[str] = None, +) -> None: + """ + Threaded version of get_kill_switches. Request is performed off thread, and callback is called when it is available. + + :param target: Target killswitch file + :param callback: The callback to pass the newly created KillSwitchSet + :param fallback: Fallback killswitch file, if any, defaults to None + """ + def make_request(): + callback(get_kill_switches(target, fallback=fallback)) + + threading.Thread(target=make_request, daemon=True).start() + + active: KillSwitchSet = KillSwitchSet([]) From 1e71955375a8ca8f3912e080e78f5cecfc45965e Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 17 Aug 2021 19:15:38 +0200 Subject: [PATCH 125/316] added check_multiple method --- killswitch.py | 24 ++++++++++++++++++++++++ tests/killswitch.py/test_killswitch.py | 18 ++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/killswitch.py b/killswitch.py index c89d7ace..7863a388 100644 --- a/killswitch.py +++ b/killswitch.py @@ -277,6 +277,25 @@ class KillSwitchSet: log.info('Rules applied successfully, allowing execution to continue') return False, new_data + def check_multiple_killswitches(self, data: UPDATABLE_DATA, *names: str, log=logger, version=_current_version): + """ + Check multiple killswitches in order. + + Note that the names are applied in the order passed, and that the first true + return from check_killswitch causes this to return + + :param data: the data to update + :param log: the logger to use, defaults to the standard EDMC main logger + :return: A tuple of bool and updated data, where the bool is true when the caller _should_ halt processing + """ + for name in names: + should_return, data = self.check_killswitch(name=name, data=data, log=log, version=version) + + if should_return: + return True, data + + return False, data + def __str__(self) -> str: """Return a string representation of KillSwitchSet.""" return f'KillSwitchSet: {str(self.kill_switches)}' @@ -465,6 +484,11 @@ def check_killswitch(name: str, data: UPDATABLE_DATA, log=logger) -> Tuple[bool, return active.check_killswitch(name, data, log) +def check_multiple_killswitches(data: UPDATABLE_DATA, *names: str, log=logger) -> tuple[bool, UPDATABLE_DATA]: + """Query the global KillSwitchSet#check_multiple method.""" + return active.check_multiple_killswitches(data, *names, log=log) + + def is_disabled(id: str, *, version: semantic_version.Version = _current_version) -> bool: """Query the global KillSwitchSet#is_disabled method.""" return active.is_disabled(id, version=version) diff --git a/tests/killswitch.py/test_killswitch.py b/tests/killswitch.py/test_killswitch.py index ebbae1b1..cda672ac 100644 --- a/tests/killswitch.py/test_killswitch.py +++ b/tests/killswitch.py/test_killswitch.py @@ -73,3 +73,21 @@ def test_operator_precedence( kill.apply_rules(cpy) assert cpy == result + + +@pytest.mark.parametrize( + ('names', 'input', 'result', 'expected_return'), + [ + (['no-actions', 'delete-action'], {'a': 1}, {'a': 1}, True), + # this is true because delete-action keyerrors, thus causing failsafe + (['delete-action'], {'a': 1}, {'a': 1}, True), + (['delete-action'], {'a': 1, 'b': {'c': 2}}, {'b': {}}, False), + ] +) +def test_check_multiple( + names: list[str], input: killswitch.UPDATABLE_DATA, result: killswitch.UPDATABLE_DATA, expected_return: bool +) -> None: + """Check that order is correct when checking multiple killswitches.""" + should_return, data = TEST_SET.check_multiple_killswitches(input, *names, version='1.0.0') + assert should_return == expected_return + assert data == result From f62f1ee97b432ed75424c7e766631b792f180b9f Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 17 Aug 2021 19:32:18 +0200 Subject: [PATCH 126/316] made types consistent and generic --- killswitch.py | 16 +++++++++------- plugins/edsm.py | 3 ++- plugins/inara.py | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/killswitch.py b/killswitch.py index 7863a388..636b7c0c 100644 --- a/killswitch.py +++ b/killswitch.py @@ -5,7 +5,7 @@ import threading from copy import deepcopy from typing import ( TYPE_CHECKING, Any, Callable, Dict, List, Mapping, MutableMapping, MutableSequence, NamedTuple, Optional, Sequence, - Tuple, TypedDict, Union, cast + Tuple, TypedDict, TypeVar, Union, cast ) import requests @@ -23,6 +23,8 @@ CURRENT_KILLSWITCH_VERSION = 2 UPDATABLE_DATA = Union[Mapping, Sequence] _current_version: semantic_version.Version = config.appversion_nobuild() +T = TypeVar('T', bound=UPDATABLE_DATA) + class SingleKill(NamedTuple): """A single KillSwitch. Possibly with additional rules.""" @@ -38,7 +40,7 @@ class SingleKill(NamedTuple): """Return whether or not this SingleKill can apply rules to a dict to make it safe to use.""" return any(x is not None for x in (self.redact_fields, self.delete_fields, self.set_fields)) - def apply_rules(self, target: UPDATABLE_DATA) -> UPDATABLE_DATA: + def apply_rules(self, target: T) -> T: """ Apply the rules this SingleKill instance has to make some data okay to send. @@ -245,8 +247,8 @@ class KillSwitchSet: return [k for k in self.kill_switches if version in k.version] def check_killswitch( - self, name: str, data: UPDATABLE_DATA, log=logger, version=_current_version - ) -> Tuple[bool, UPDATABLE_DATA]: + self, name: str, data: T, log=logger, version=_current_version + ) -> Tuple[bool, T]: """ Check whether or not a killswitch is enabled. If it is, apply rules if any. @@ -277,7 +279,7 @@ class KillSwitchSet: log.info('Rules applied successfully, allowing execution to continue') return False, new_data - def check_multiple_killswitches(self, data: UPDATABLE_DATA, *names: str, log=logger, version=_current_version): + def check_multiple_killswitches(self, data: T, *names: str, log=logger, version=_current_version) -> Tuple[bool, T]: """ Check multiple killswitches in order. @@ -479,12 +481,12 @@ def get_disabled(id: str, *, version: semantic_version.Version = _current_versio return active.get_disabled(id, version=version) -def check_killswitch(name: str, data: UPDATABLE_DATA, log=logger) -> Tuple[bool, UPDATABLE_DATA]: +def check_killswitch(name: str, data: T, log=logger) -> Tuple[bool, T]: """Query the global KillSwitchSet#check_killswitch method.""" return active.check_killswitch(name, data, log) -def check_multiple_killswitches(data: UPDATABLE_DATA, *names: str, log=logger) -> tuple[bool, UPDATABLE_DATA]: +def check_multiple_killswitches(data: T, *names: str, log=logger) -> tuple[bool, T]: """Query the global KillSwitchSet#check_multiple method.""" return active.check_multiple_killswitches(data, *names, log=log) diff --git a/plugins/edsm.py b/plugins/edsm.py index e8e3c81d..8b35aea0 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -407,7 +407,8 @@ def journal_entry( # noqa: C901, CCR001 if should_return: return - entry = cast(MutableMapping[str, Any], new_entry) + + entry = new_entry this.on_foot = state['OnFoot'] if entry['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked'): diff --git a/plugins/inara.py b/plugins/inara.py index 74520dcb..a3e2a22c 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -350,7 +350,7 @@ def journal_entry( # noqa: C901, CCR001 # this can and WILL break state, but if we're concerned about it sending bad data, we'd disable globally anyway return '' - entry = cast(Dict[str, Any], new_entry) + entry = new_entry this.on_foot = state['OnFoot'] event_name: str = entry['event'] this.cmdr = cmdr From 6c7245ee3d368b54ddd0df1e93728e4fafda9085 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 17 Aug 2021 19:37:26 +0200 Subject: [PATCH 127/316] updated eddn to new killswitches --- docs/Killswitches.md | 2 +- plugins/eddn.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 85275819..8864f96c 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -92,7 +92,7 @@ The current recognised (to EDMC and its internal plugins) killswitch strings are | Kill Switch | Supported Plugins | Description | | :------------------------------------------- | :---------------------: | :---------------------------------------------------------------------------------------- | -| *`plugins.eddn.send` | eddn | Disables all use of the send method on EDDN (effectively disables EDDN updates) | +| `plugins.eddn.send` | eddn | Disables all use of the send method on EDDN (effectively disables EDDN updates) | | `plugins.<plugin>.journal` | eddn, inara, edsm, eddb | Disables all journal processing for the plugin | | `plugins.<plugin>.worker` | edsm, inara | Disables the plugins worker thread (effectively disables updates) (does not close thread) | | `plugins.<plugin>.worker.<eventname>` | edsm, inara | Disables the plugin worker for the given eventname | diff --git a/plugins/eddn.py b/plugins/eddn.py index e532c8a8..3d0307fa 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -168,10 +168,13 @@ class EDDN: :param cmdr: the CMDR to use as the uploader ID. :param msg: the payload to send. """ - if (res := killswitch.get_disabled('plugins.eddn.send')).disabled: - logger.warning(f"eddn.send has been disabled via killswitch. Returning. ({res.reason})") + should_return, new_data = killswitch.check_killswitch('plugins.eddn.send', msg) + if should_return: + logger.warning('eddn.send has been disabled via killswitch. Returning.') return + msg = new_data + uploader_id = cmdr to_send: OrderedDictT[str, OrderedDict[str, Any]] = OrderedDict([ @@ -787,15 +790,18 @@ def journal_entry( # noqa: C901, CCR001 :param state: `dict` - Current `monitor.state` data. :return: `str` - Error message, or `None` if no errors. """ - if (ks := killswitch.get_disabled("plugins.eddn.journal")).disabled: - logger.warning(f'EDDN journal handler has been disabled via killswitch: {ks.reason}') + + 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 return None - elif (ks := killswitch.get_disabled(f'plugins.eddn.journal.event.{entry["event"]}')).disabled: - logger.warning(f'Handling of event {entry["event"]} disabled via killswitch: {ks.reason}') + should_return, new_data = killswitch.check_killswitch(f'plugins.eddn.journal.event.{entry["event"]}', new_data) + if should_return: return None + entry = new_data + # Recursively filter '*_Localised' keys from dict def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: filtered: OrderedDictT[str, Any] = OrderedDict() From bb882d633a023f73eca7a94aa43382b95bf7604b Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 17 Aug 2021 19:41:08 +0200 Subject: [PATCH 128/316] clarified docs around inara --- docs/Killswitches.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 8864f96c..8b6c9398 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -94,13 +94,16 @@ The current recognised (to EDMC and its internal plugins) killswitch strings are | :------------------------------------------- | :---------------------: | :---------------------------------------------------------------------------------------- | | `plugins.eddn.send` | eddn | Disables all use of the send method on EDDN (effectively disables EDDN updates) | | `plugins.<plugin>.journal` | eddn, inara, edsm, eddb | Disables all journal processing for the plugin | -| `plugins.<plugin>.worker` | edsm, inara | Disables the plugins worker thread (effectively disables updates) (does not close thread) | +| `plugins.<plugin>.worker` | edsm, *inara | Disables the plugins worker thread (effectively disables updates) (does not close thread) | | `plugins.<plugin>.worker.<eventname>` | edsm, inara | Disables the plugin worker for the given eventname | | `plugins.<plugin>.journal.event.<eventname>` | eddn, inara, edsm | Specific events to disable processing for. | Killswitches marked with `*` do **not** support modification of their values via set/redact/delete. And as such any match will simply stop processing. +For `plugin.inara.worker`, events are checked individually later by the eventname version. Use that to modify individual +inara events + ## File location The main killswitch file is kept in the `releases` branch on the EDMC github repo. The file should NEVER be committed to From c86b9790ca93db21a4e21e8d37046a8f45bae577 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Tue, 17 Aug 2021 19:41:28 +0200 Subject: [PATCH 129/316] updated eddb to new killswitches --- plugins/eddb.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/eddb.py b/plugins/eddb.py index 80fd9431..220046f0 100644 --- a/plugins/eddb.py +++ b/plugins/eddb.py @@ -94,14 +94,14 @@ def prefs_changed(cmdr, is_beta): def journal_entry(cmdr, is_beta, system, station, entry, state): - if (ks := killswitch.get_disabled('plugins.eddb.journal')).disabled: - logger.warning(f'Journal processing for EDDB has been disabled: {ks.reason}') + should_return, new_entry = killswitch.check_killswitch('plugins.eddb.journal', entry) + if should_return: # LANG: Journal Processing disabled due to an active killswitch plug.show_error(_('EDDB Journal processing disabled. See Log.')) return - elif (ks := killswitch.get_disabled(f'plugins.eddb.journal.event.{entry["event"]}')).disabled: - logger.warning(f'Processing of event {entry["event"]} has been disabled: {ks.reason}') + should_return, new_entry = killswitch.check_killswitch(f'plugins.eddb.journal.event.{entry["event"]}', new_entry) + if should_return: return this.on_foot = state['OnFoot'] From 5a080c8e55febfa9d0c329baa4fa706bf55167cf Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 19 Aug 2021 19:49:39 +0200 Subject: [PATCH 130/316] updated docs --- docs/Killswitches.md | 71 +++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 8b6c9398..92c3d317 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -1,10 +1,13 @@ # Kill Switches -EDMarketConnector implements a Kill Switch system that allows us to disable features based on a version mask. Meaning that we can stop major bugs from affecting the services we support, at the expense of disabling that support. +EDMarketConnector implements a Kill Switch system that allows us to disable +features based on a version mask. Meaning that we can stop major bugs from +affecting the services we support, at the expense of disabling that support. ## Format -Killswitches are stored in a JSON file that is queried by EDMC on startup. The format is as follows: +Killswitches are stored in a JSON file that is queried by EDMC on startup. +The format is as follows: | Key | Type | Description | | --------------: | :------: | :------------------------------------------------------------------------------------------- | @@ -12,15 +15,17 @@ Killswitches are stored in a JSON file that is queried by EDMC on startup. The f | `last_updated` | `string` | When last the kill switches were updated (for human use only) | | `kill_switches` | `array` | The kill switches this file contains (expanded below) | -The `kill_switches` array contains kill switch objects. Each contains the following fields: +The `kill_switches` array contains kill switch objects. Each contains the +following fields: | Key | Type | Description | | --------: | :---------------------------: | :--------------------------------------------------------------------------- | | `version` | `version spec (see Versions)` | The version of EDMC these kill switches apply to (Must be valid semver spec) | | `kills` | `Dict[str, Dict[...]]` | The various keys disabled -> definition of the killswitch behaviour | -Each entry in `kills` must contain at least a `reason` field describing why the killswitch was added. EDMC will show -this to the user (for internal killswitches, anyway). +Each entry in `kills` must contain at least a `reason` field describing why the +killswitch was added. EDMC will show this to the user +(for internal killswitches, anyway). | Key (* = required) | Type | Description | | -----------------: | :--------------: | :-------------------------------------------------------------------------------------------- | @@ -29,8 +34,8 @@ this to the user (for internal killswitches, anyway). | `redact_fields` | `List[str]` | A list of fields to redact. This is equivalent to setting the fields to the string "REDACTED" | | `delete_fields` | `List[str]` | A list of fields in the matching event to be removed, if they exist. | -The order listed above is the precedence for actions. i.e. All set fields are set, then all redact fields are redacted -then all delete fields are deleted. +The order listed above is the precedence for actions. i.e. All set fields are +set, then all redact fields are redacted then all delete fields are deleted. An example follows: @@ -60,35 +65,42 @@ An example follows: } ``` -- `plugins.edsm.send` will have fields deleted, set, and redacted, and then will *not* be halted, the send will continue with the modified data. -- `plugins.some_plugin.some_thing` will never be allowed to continue (as all fields are blank) +- `plugins.edsm.send` will have fields deleted, set, and redacted, and then + will *not* be halted, the send will continue with the modified data. +- `plugins.some_plugin.some_thing` will never be allowed to continue + (as all fields are blank) ### Versions -Versions are checked using contains checks on `semantic_version.SimpleSpec` instances. SimpleSpec supports both specific -versions (`1.2.3`), non-specific ranges (`1.0` will match `1.0.1` and `1.0.5` etc), wildcards (`1.2.*`), +Versions are checked using contains checks on `semantic_version.SimpleSpec` +instances. SimpleSpec supports both specific versions (`1.2.3`), non-specific +ranges (`1.0` will match `1.0.1` and `1.0.5` etc), wildcards (`1.2.*`), and ranges (`<1.0.0`, `>=2.0.0`) ## Plugin support -Plugins may use the killswitch system simply by hosting their own version of the killswitch file, and fetching it -using `killswitch.get_kill_switches(target='https://example.com/myplugin_killswitches.json')`. The returned object can -be used to query the kill switch set, see the docstrings for more information on specifying versions. +Plugins may use the killswitch system simply by hosting their own version of the +killswitch file, and fetching it using +`killswitch.get_kill_switches(target='https://example.com/myplugin_killswitches.json')`. +The returned object can be used to query the kill switch set, see the docstrings +for more information on specifying versions. -A helper method `killswitch.get_kill_switch_thread` is provided to allow for simple nonblocking requests for -KillSwitches. It starts a new thread, performs the HTTP request, and sends the results to the given callback. +A helper method `killswitch.get_kill_switch_thread` is provided to allow for +simple nonblocking requests for KillSwitches. It starts a new thread, performs +the HTTP request, and sends the results to the given callback. -**Note that your callback is invoked off-thread. Take precaution for locking if required, and do _NOT_ use tkinter** -**methods** +**Note that your callback is invoked off-thread. Take precaution for locking** +**if required, and do _NOT_ use tkinter methods** -The version of the JSON file will be automatically upgraded if possible by the code KillSwitch code. -No behaviour changes will occur--Any killswitches defined in older versions will simply become unconditional kills in -the new version. +The version of the JSON file will be automatically upgraded if possible by the +code KillSwitch code. No behaviour changes will occur, any killswitches defined +in older versions will simply become unconditional kills in the new version. ## Currently supported killswitch strings -The current recognised (to EDMC and its internal plugins) killswitch strings are as follows: +The current recognised (to EDMC and its internal plugins) killswitch strings are +as follows: | Kill Switch | Supported Plugins | Description | | :------------------------------------------- | :---------------------: | :---------------------------------------------------------------------------------------- | @@ -98,14 +110,17 @@ The current recognised (to EDMC and its internal plugins) killswitch strings are | `plugins.<plugin>.worker.<eventname>` | edsm, inara | Disables the plugin worker for the given eventname | | `plugins.<plugin>.journal.event.<eventname>` | eddn, inara, edsm | Specific events to disable processing for. | -Killswitches marked with `*` do **not** support modification of their values via set/redact/delete. And as such any match -will simply stop processing. +Killswitches marked with `*` do **not** support modification of their values via +set/redact/delete. And as such any match will simply stop processing. -For `plugin.inara.worker`, events are checked individually later by the eventname version. Use that to modify individual -inara events +For `plugin.inara.worker`, events are checked individually later by the +eventname version. Use that to modify individual inara events. This is due to +inara event batching meaning that the data that would be passed to `.worker` +would not be in a form that could be easily understood (except to blank it) ## File location -The main killswitch file is kept in the `releases` branch on the EDMC github repo. The file should NEVER be committed to -any other repos. In the case that the killswitch file is found in other repos, the one in releases should always +The main killswitch file is kept in the `releases` branch on the EDMC github +repo. The file should NEVER be committed to any other repos. In the case that +the killswitch file is found in other repos, the one in releases should always be taken as correct regardless of others. From cc17f21cc58fcaacb0d47aa9c23504468202c7aa Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 19 Aug 2021 19:54:56 +0200 Subject: [PATCH 131/316] s/\t/\s\s\s\s/g --- docs/Releasing.md | 97 ++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/docs/Releasing.md b/docs/Releasing.md index 7c86384f..acf3d50c 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -49,14 +49,14 @@ You will need several pieces of software installed, or the files from their `requirements-dev.txt`. 1. You'll now need to 'pip install' several python modules. - 1. Ensure you have `pip` installed. If needs be see - [Installing pip](https://pip.pypa.io/en/stable/installing/) - 1. The easiest way is to utilise the `requirements-dev.txt` file: - `python -m pip install --user -r requirements-dev.txt`. This will install - all dependencies plus anything required for development. - 1. Else check the contents of both `requirements.txt` and `requirements-dev.txt`, - and ensure the modules listed there are installed as per the version - requirements. + 1. Ensure you have `pip` installed. If needs be see + [Installing pip](https://pip.pypa.io/en/stable/installing/) + 1. The easiest way is to utilise the `requirements-dev.txt` file: + `python -m pip install --user -r requirements-dev.txt`. This will install + all dependencies plus anything required for development. + 1. Else check the contents of both `requirements.txt` and `requirements-dev.txt`, + and ensure the modules listed there are installed as per the version + requirements. If you are using different versions of any of these tools then please ensure that the paths where they're installed match the associated lines in @@ -109,16 +109,17 @@ that. 3. `appcmdname`: The CLI appname, e.g. 'EDMC' 4. `_static_appversion`: The current version, e.g. `4.0.2`. **You MUST make this something like `4.0.2+<myversion>` to differentiate it from - upstream.** Whatever is in this field is what will be reported if - sending messages to EDDN, or any of the third-party website APIs. - This is utilising the 'build metadata' part of a Semantic version. + upstream.** Whatever is in this field is what will be reported if + sending messages to EDDN, or any of the third-party website APIs. + This is utilising the 'build metadata' part of a Semantic version. 5. `copyright`: The Copyright string. 6. `update_feed`: The URL where the application looks for current latest - version information. This URL should be hosting a renamed (so the full - URL doesn't change over application versions) version of the - appcast_win_<version>.xml file. The original upstream value is - `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml`. + version information. This URL should be hosting a renamed (so the full + URL doesn't change over application versions) version of the + appcast_win_<version>.xml file. The original upstream value is + `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml`. + 1. Location of release files. This needs to be cited correctly in the `edmarketconnector.xml` file, which is what the application queries to see if there is a newer version. @@ -201,10 +202,10 @@ which branch (stable or beta) you'll be ultimately updating. 1. So as to make backing out any mistakes easier create a new branch for this release, using a name like `release-4.0.2`. Do not use the tag `Release/4.0.2` form, that could cause confusion. - 1. `git checkout stable` # Or whichever other branch is appropriate. - 1. `git pull origin` # Ensures local branch is up to date. - 1. `git checkout -b release-4.0.2` - + 1. `git checkout stable` # Or whichever other branch is appropriate. + 1. `git pull origin` # Ensures local branch is up to date. + 1. `git checkout -b release-4.0.2` + 1. Get all the relevant code changes into this branch. This might mean merging from another branch, such as an issue-specific one, or possibly cherry-picking commits. See [Contributing Guidelines](../Contributing.md) @@ -218,12 +219,12 @@ commit for this change.** 1. Prepare a changelog text for the release. You'll need this both for the GitHub release and the contents of the `edmarketconnector.xml` file if making a `stable` release, as well as any social media posts you make. - 1. The primary location of the changelog is [Changelog.md](../Changelog.md) - - update this first. - 1. To be sure you include all the changes look at the git log since the - prior appropriate (pre-)release. - 1. As you're working in a version-specific branch, `release-4.0.2`, you - can safely commit these changes and push to GitHub. + 1. The primary location of the changelog is [Changelog.md](../Changelog.md) - + update this first. + 1. To be sure you include all the changes look at the git log since the + prior appropriate (pre-)release. + 1. As you're working in a version-specific branch, `release-4.0.2`, you + can safely commit these changes and push to GitHub. **Do not merge the branch with `releases` until the GitHub release is in place.** If you're wondering, you needed to get the changelog prepared before building @@ -244,36 +245,36 @@ a 'Git bash' window. The 'Terminal' tab of PyCharm works fine. Assuming the correct python.exe is associated with .py files then simply run: - setup.py py2exe + setup.py py2exe else you might need this, which assumes correct python.exe is in your PATH: - python.exe setup.py py2exe - + python.exe setup.py py2exe + else you'll have to specify the path to python.exe, e.g.: - "C:\Program Files \(x86)\Python38-32\python.exe" setup.py py2exe + "C:\Program Files \(x86)\Python38-32\python.exe" setup.py py2exe Output will be something like (`...` denoting parts elided for brevity): - running py2exe - ... - Building 'dist.win32\EDMC.exe'. - Building 'dist.win32\EDMarketConnector.exe'. - Building shared code archive 'dist.win32\library.zip'. - ... - Windows Installer XML Toolset Compiler version 3.11.1.2318 - Copyright (c) .NET Foundation and contributors. All rights reserved. - ... - Package language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 1029, Database codepage = 0 - MsiTran V 5.0 - Copyright (c) Microsoft Corporation. All Rights Reserved - ... - DonePackage language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 0, Database codepage = 0 - MsiTran V 5.0 - Copyright (c) Microsoft Corporation. All Rights Reserved + running py2exe + ... + Building 'dist.win32\EDMC.exe'. + Building 'dist.win32\EDMarketConnector.exe'. + Building shared code archive 'dist.win32\library.zip'. + ... + Windows Installer XML Toolset Compiler version 3.11.1.2318 + Copyright (c) .NET Foundation and contributors. All rights reserved. + ... + Package language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 1029, Database codepage = 0 + MsiTran V 5.0 + Copyright (c) Microsoft Corporation. All Rights Reserved + ... + DonePackage language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 0, Database codepage = 0 + MsiTran V 5.0 + Copyright (c) Microsoft Corporation. All Rights Reserved - Done + Done **Do check the output** for things like not properly specifying extra files to be included in the install. If they're not picked up by current rules in @@ -322,9 +323,9 @@ Once that is done then for manually built installers: 2. Create a matching `hashes.sum` file for your `.msi` file: sha256sum EDMarketConnector_win*.msi > ./hashes.sum - + and replace the one in the draft release with this. - + But, **again, you should just be using the auto-build mechanism**. From 27c18712509a796c8c7218e7115ac21968719275 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 19 Aug 2021 20:00:52 +0200 Subject: [PATCH 132/316] cleanup --- docs/Releasing.md | 109 ++++++++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/docs/Releasing.md b/docs/Releasing.md index acf3d50c..673820a0 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -79,7 +79,7 @@ version strings. a wider audience to test forthcoming changes. 1. `-rc<serial>`, i.e. `-rc1`. This is used when testing has shown this code should be ready for full release, but you want even wider testing. - + In both these cases simply increment `<serial>` for each new release. *Do* start from `1` again when beginning `-rc` releases. @@ -88,6 +88,7 @@ version strings. There are some things that you should always change before running your own version of EDMC + 1. The Frontier CAPI client ID. This is hardcoded in companion.py, but can be overridden by setting a CLIENT_ID environment variable. @@ -118,21 +119,22 @@ that. URL doesn't change over application versions) version of the appcast_win_<version>.xml file. The original upstream value is `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml`. - -1. Location of release files. This needs to be cited correctly in the +2. Location of release files. This needs to be cited correctly in the `edmarketconnector.xml` file, which is what the application queries to see if there is a newer version. Look for the `url="...` line in the `<enclosure ...` that is like: - <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" - /> + ```xml + <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" + /> + ``` ## Adding a new file @@ -151,28 +153,33 @@ You will *also* need to add the file to the `EDMarketConnector.wxs` file so that it's actually included in the installer. 1. Location the the appropriate part of the: + ```xml <Directory Id="ProgramFilesFolder"> ``` section and add a new sub-section: + ```xml <Component Id="<valid_component_id>" Guid=""*"> <File KeyPath="yes" Source="SourceDir\\<file name>" /> </Component> ``` - + Note that you only need `Id="<valid_component_id>"` if the filename itself is not a valid Id, e.g. because it contains spaces. - + If the new file is in a new sub-directory then you'll need to add that as well. See the `L10n` example. - -1. Now find the: + +2. Now find the: + ```xml <Feature Id='Complete' Level='1'> ``` + section and add an appropriate line to it. Remember to use either the specific Id you set above or the filename (without directory) for this: + ```xml <ComponentRef Id="<valid_component_id>" /> ``` @@ -205,7 +212,7 @@ release, using a name like `release-4.0.2`. Do not use the tag 1. `git checkout stable` # Or whichever other branch is appropriate. 1. `git pull origin` # Ensures local branch is up to date. 1. `git checkout -b release-4.0.2` - + 1. Get all the relevant code changes into this branch. This might mean merging from another branch, such as an issue-specific one, or possibly cherry-picking commits. See [Contributing Guidelines](../Contributing.md) @@ -245,36 +252,44 @@ a 'Git bash' window. The 'Terminal' tab of PyCharm works fine. Assuming the correct python.exe is associated with .py files then simply run: - setup.py py2exe +```batch +setup.py py2exe +``` else you might need this, which assumes correct python.exe is in your PATH: - python.exe setup.py py2exe - +```batch +python.exe setup.py py2exe +``` + else you'll have to specify the path to python.exe, e.g.: - "C:\Program Files \(x86)\Python38-32\python.exe" setup.py py2exe +```batch +"C:\Program Files \(x86)\Python38-32\python.exe" setup.py py2exe +``` Output will be something like (`...` denoting parts elided for brevity): - running py2exe - ... - Building 'dist.win32\EDMC.exe'. - Building 'dist.win32\EDMarketConnector.exe'. - Building shared code archive 'dist.win32\library.zip'. - ... - Windows Installer XML Toolset Compiler version 3.11.1.2318 - Copyright (c) .NET Foundation and contributors. All rights reserved. - ... - Package language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 1029, Database codepage = 0 - MsiTran V 5.0 - Copyright (c) Microsoft Corporation. All Rights Reserved - ... - DonePackage language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 0, Database codepage = 0 - MsiTran V 5.0 - Copyright (c) Microsoft Corporation. All Rights Reserved +```plaintext +running py2exe +... +Building 'dist.win32\EDMC.exe'. +Building 'dist.win32\EDMarketConnector.exe'. +Building shared code archive 'dist.win32\library.zip'. +... +Windows Installer XML Toolset Compiler version 3.11.1.2318 +Copyright (c) .NET Foundation and contributors. All rights reserved. +... +Package language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 1029, Database codepage = 0 +MsiTran V 5.0 +Copyright (c) Microsoft Corporation. All Rights Reserved +... +DonePackage language = 1033,1029,1031,1034,1035,1036,1038,1040,1041,1043,1045,1046,1049,1058,1062,2052,2070,2074,0, ProductLanguage = 0, Database codepage = 0 +MsiTran V 5.0 +Copyright (c) Microsoft Corporation. All Rights Reserved - Done +Done +``` **Do check the output** for things like not properly specifying extra files to be included in the install. If they're not picked up by current rules in @@ -354,7 +369,7 @@ the release-4.0.2 branch, as it's temporary.* `[ ] This is a pre-release` box.** Not doing so will cause this release to be pointed to by the 'latest' URL. 5. We always create a discussion for any new release, so tick the - `Create a discussion for this release ` box, and check it's targeted at + `Create a discussion for this release` box, and check it's targeted at the `Announcement` category. Once the release is created, then **only if making a `stable` release** @@ -365,8 +380,8 @@ changelog text to the correct section(s): into `stable` above. 3. Use the following to generate HTML from the MarkDown (`pip install grip` first if you need to): + `grip --export ChangeLog.md` - grip --export ChangeLog.md 4. Open `edmarketconnector.xml` in your editor. 5. If there's still a Mac OS section croll down past it to the Windows section. @@ -380,7 +395,7 @@ changelog text to the correct section(s): If, for instance, you fail to *update* this URL then upon running the 'new' installer it will silently fail, because you made people try to install the old version over the old version. - 3. Yes, `sparkle:version` should be the Semantic Version string, + 2. Yes, `sparkle:version` should be the Semantic Version string, not the Windows A.B.C.D form. 8. `git push origin` @@ -388,7 +403,7 @@ changelog text to the correct section(s): EDMC instances to pick up on 'Check for Updates'. The WinSparkle check for updates specifically targets: - `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml` + `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml` as per `config.py` `update_feed`. @@ -405,9 +420,9 @@ If you are making a pre-release then: 1. **DO NOT** Edit `edmarketconnector.xml` at all. No, not even if you think you won't accidentally merge it into `releases`. Just don't change it at all. -3. **DO NOT** merge into `releases`. -4. **DO NOT** merge into `stable`. -5. *Do* merge the code into `beta` after you have made a 'pre-release' on +1. **DO NOT** merge into `releases`. +1. **DO NOT** merge into `stable`. +1. *Do* merge the code into `beta` after you have made a 'pre-release' on GitHub. # Changing Python version @@ -420,11 +435,11 @@ When changing the Python version (Major.Minor.Patch) used: 1. `.github/workflows/windows-build.yml` needs updating to have the GitHub based build use the correct version. - + 1. Major or Minor level changes: 1. `setup.py` will need its version check updating. - 1. `EDMarketConnector.wxs` will need updating to reference the correct + 2. `EDMarketConnector.wxs` will need updating to reference the correct pythonXX.dll file. - 1. `.pre-commit-config.yaml` will need the `default_language_version` + 3. `.pre-commit-config.yaml` will need the `default_language_version` section updated to the appropriate version. From 1b45c562c1d0cc0524527a5d23acc752dbfc34af Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 19 Aug 2021 20:01:48 +0200 Subject: [PATCH 133/316] added note about killswitch_test.py --- docs/Releasing.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Releasing.md b/docs/Releasing.md index 673820a0..37d14ae6 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -245,6 +245,9 @@ to be sent over EDDN, then you should add an appropriate entry to the killswitches.json file *in the `releases` branch*. That file **must only ever be committed to the `releases` branch!!!** See [docs/Killswitches.md](Killswitches.md). +Killswitch files can and should be verified using the `killswitch_test.py` +script in the `scripts` directory + # Packaging & Installer Generation You'll want to do the .exe and .msi generation in a `cmd.exe` window, not e.g. From 3e5efb03ec4f571323fe2c4a6c76507d124ab00f Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 19 Aug 2021 20:34:13 +0200 Subject: [PATCH 134/316] mentioned killswitch test script --- docs/Killswitches.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 92c3d317..5db6f11e 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -70,6 +70,11 @@ An example follows: - `plugins.some_plugin.some_thing` will never be allowed to continue (as all fields are blank) +### Testing + +Killswitch files can be tested using the script in `scripts/killswitch_test.py`. +Providing a file as an argument or `-` for stdin will output the behaviour of +the provided file, including indicating typos, if applicable. ### Versions From 8134a0473929e76718a34b0cf2bc9f1e7a4066bb Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 19 Aug 2021 20:46:02 +0200 Subject: [PATCH 135/316] clarified docstrings --- killswitch.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/killswitch.py b/killswitch.py index 636b7c0c..a43c6679 100644 --- a/killswitch.py +++ b/killswitch.py @@ -217,7 +217,6 @@ class KillSwitchSet: current EDMC version :return: a namedtuple indicating status and reason, if any """ - if isinstance(version, str): version = semantic_version.Version.coerce(version) @@ -254,7 +253,7 @@ class KillSwitchSet: :param name: The killswitch to check :param data: The data to modify if needed - :return: A bool indicating if the caller should return, and either the + :return: A two tuple consisting of: A bool indicating if the caller should return, and either the original data or a *COPY* that has been modified by rules """ res = self.get_disabled(name, version=version) @@ -288,7 +287,7 @@ class KillSwitchSet: :param data: the data to update :param log: the logger to use, defaults to the standard EDMC main logger - :return: A tuple of bool and updated data, where the bool is true when the caller _should_ halt processing + :return: A two tuple of bool and updated data, where the bool is true when the caller _should_ halt processing """ for name in names: should_return, data = self.check_killswitch(name=name, data=data, log=log, version=version) From dac13216a02d4ef54b12e70751ce04fd9a6b05b3 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Thu, 19 Aug 2021 20:50:41 +0200 Subject: [PATCH 136/316] remove uneeded cast and fix bug in passed data --- plugins/edsm.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index 8b35aea0..33a39804 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -407,7 +407,7 @@ def journal_entry( # noqa: C901, CCR001 if should_return: return - + entry = new_entry this.on_foot = state['OnFoot'] @@ -646,14 +646,16 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently retrying = 0 while retrying < 3: should_skip, new_item = killswitch.check_killswitch( - 'plugins.edsm.worker', item if item is not None else {}, logger + 'plugins.edsm.worker', + item if item is not None else cast(Tuple[str, Mapping[str, Any]], ("", {})), + logger ) if should_skip: break if item is not None: - item = cast(Tuple[str, Mapping[str, Any]], new_item) + item = new_item try: if item and entry['event'] not in this.discarded_events: @@ -669,7 +671,7 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently if skip: continue - new_pending.append(cast(Mapping[str, Any], new)) + new_pending.append(new) pending = new_pending From b86a0105965df892fdcf7f62066e1a0994865e8d Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sun, 22 Aug 2021 09:03:36 +0200 Subject: [PATCH 137/316] updated docs further --- docs/Killswitches.md | 60 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 5db6f11e..7e14bfb8 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -31,8 +31,8 @@ killswitch was added. EDMC will show this to the user | -----------------: | :--------------: | :-------------------------------------------------------------------------------------------- | | `reason`* | `str` | The reason that this killswitch was added | | `set_fields` | `Dict[str, Any]` | A map of key -> contents to update (or overwrite) existing data with | -| `redact_fields` | `List[str]` | A list of fields to redact. This is equivalent to setting the fields to the string "REDACTED" | -| `delete_fields` | `List[str]` | A list of fields in the matching event to be removed, if they exist. | +| `redact_fields` | `List[str]` | A list of traversal paths to redact. This is the same as using set with a value of "REDACTED" | +| `delete_fields` | `List[str]` | A list of traversal paths in the matching event to be removed, if they exist. | The order listed above is the precedence for actions. i.e. All set fields are set, then all redact fields are redacted then all delete fields are deleted. @@ -70,6 +70,52 @@ An example follows: - `plugins.some_plugin.some_thing` will never be allowed to continue (as all fields are blank) +Indexing of lists (and any other `Sequence`) is done with numbers as normal. +Negative numbers do work to reference the end of sequences. + +### Traversal paths + +`set_fields`, `delete_fields`, and `redact_fields` all accept a single traversal +path to target particular fields. + +When following paths, there are some caveats one should know. + +- If a field (for example `a.b`) exists in the data dict + (ie, `{'a.b': True}`) it will be the thing accessed by a killswitch that + includes `a.b` at the current "level". This means that if both a key `a.b` + and a nested key exist with the same name (`{'a.b': 0, 'a': { 'b': 1 }}`), + the former is always referenced. No backtracking takes place to attempt to + resolve conflicts, even if the dotted field name is not at the end of a key. + +- Traversal can and will error, especially if you attempt to pass though keys + that do not exist (eg past the end of a `Sequence` or a key that does not + exist in a `Mapping`). + +- If any exception occurs during your traversal, **the entire killswitch is** + **assumed to be faulty, and will be treated as a permanent killswitch** + +### Individual action rules + +All actions (as noted above) can fail during traversal to their targets. If this +happens, the entire killswitch is assumed to be a permanent one and execution +stops. + +#### Deletes + +For both `MutableMapping` and `MutableSequence` targets, deletes never fail. +If a key doesn't exist it simply is a no-op. + +#### Sets + +Sets always succeed for `MutableMapping` objects, either creating new keys +or updating existing ones. For `MutableSequence`s, however, a set can fail if +the index is not within `len(target)`. If the index is exactly `len(target)`, +`append` is used. + +You can set values other than strings, but you are limited to what json itself +supports, and the translation thereof to python. In general this means: +`int`, `float`, `string`, `bool`, and `None` (json `null`) values may be set. + ### Testing Killswitch files can be tested using the script in `scripts/killswitch_test.py`. @@ -86,7 +132,7 @@ and ranges (`<1.0.0`, `>=2.0.0`) ## Plugin support Plugins may use the killswitch system simply by hosting their own version of the -killswitch file, and fetching it using +killswitch file, and fetching it using `killswitch.get_kill_switches(target='https://example.com/myplugin_killswitches.json')`. The returned object can be used to query the kill switch set, see the docstrings for more information on specifying versions. @@ -125,7 +171,7 @@ would not be in a form that could be easily understood (except to blank it) ## File location -The main killswitch file is kept in the `releases` branch on the EDMC github -repo. The file should NEVER be committed to any other repos. In the case that -the killswitch file is found in other repos, the one in releases should always -be taken as correct regardless of others. +The main killswitch file (`killswitches_v2.json`) is kept in the `releases` +branch on the EDMC github repo. The file should NEVER be committed to any other +repos. In the case that the killswitch file is found in other repos, the one in +releases should always be taken as correct regardless of others. From afa0217ed86ed9c6fe6985de224e0bf4cd05e42a Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sun, 22 Aug 2021 21:45:02 +0200 Subject: [PATCH 138/316] additional info in docstrings --- killswitch.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/killswitch.py b/killswitch.py index a43c6679..a7701739 100644 --- a/killswitch.py +++ b/killswitch.py @@ -47,6 +47,7 @@ class SingleKill(NamedTuple): Note that this MODIFIES DATA IN PLACE. :param target: data to apply a rule to + :raises: Any and all exceptions _deep_apply and _apply can raise. """ for key, value in (self.set_fields if self .set_fields is not None else {}).items(): _deep_apply(target, key, value) @@ -69,6 +70,7 @@ def _apply(target: UPDATABLE_DATA, key: str, to_set: Any = None, delete: bool = :param to_set: the data to set, if any, defaults to None :param delete: whether or not to delete the key or index, defaults to False :raises ValueError: when an unexpected target type is passed + :raises IndexError: when an invalid index is set or deleted """ if isinstance(target, MutableMapping): if delete: @@ -106,6 +108,8 @@ def _deep_apply(target: UPDATABLE_DATA, path: str, to_set=None, delete=False): :param target: the dict to modify :param to_set: the data to set, defaults to None :param delete: whether or not to delete the key rather than set it + :raises IndexError: when an invalid index is traversed into + :raises KeyError: when an invalid key is traversed into """ current = target key: str = "" From e8cd29442f64a03a4150742963ac87498c1baefc Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sun, 22 Aug 2021 21:46:02 +0200 Subject: [PATCH 139/316] fixed missing data types --- docs/Killswitches.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index 7e14bfb8..d0fa814b 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -113,8 +113,10 @@ the index is not within `len(target)`. If the index is exactly `len(target)`, `append` is used. You can set values other than strings, but you are limited to what json itself -supports, and the translation thereof to python. In general this means: -`int`, `float`, `string`, `bool`, and `None` (json `null`) values may be set. +supports, and the translation thereof to python. In general this means that only +JSON primitives and their python equivalents +(`int`, `float`, `string`, `bool`, and `None` (json `null`)), and +json compound types (`object -- {}` and `array -- []`) may be set. ### Testing From 88a7de6dd039d098c912d9d1168f7208d2a2c4af Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Sun, 22 Aug 2021 22:18:50 +0200 Subject: [PATCH 140/316] removed redundant test --- tests/killswitch.py/test_apply.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/killswitch.py/test_apply.py b/tests/killswitch.py/test_apply.py index a99c55e0..c199ec48 100644 --- a/tests/killswitch.py/test_apply.py +++ b/tests/killswitch.py/test_apply.py @@ -81,8 +81,6 @@ def test_get_int(input: str, expected: Optional[int]) -> None: ({'multilevel.keys': {'with.dots': False}}, 'multilevel.keys.with.dots', '', True, {'multilevel.keys': {'with.dots': True}}), ({'dotted.key.one.level': False}, 'dotted.key.one.level', '', True, {'dotted.key.one.level': True}), - ({'dotted.key.one.level': {"oops.i.lied": False}}, 'dotted.key.one.level.oops.i.lied', - '', True, {'dotted.key.one.level': {"oops.i.lied": True}}), ], ) def test_deep_get(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, result: UPDATABLE_DATA) -> None: From 0eb3056236a6864fb564388333b1eabdb59712a1 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Mon, 23 Aug 2021 12:46:57 +0200 Subject: [PATCH 141/316] resolved bug in wildcard detection --- scripts/killswitch_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/killswitch_test.py b/scripts/killswitch_test.py index 314e3344..1b2f54a7 100644 --- a/scripts/killswitch_test.py +++ b/scripts/killswitch_test.py @@ -45,7 +45,10 @@ def match_exists(match: str) -> tuple[bool, str]: if known_split == split_match: return True, "" - matched_fields = sum(len(k) for k, s in zip(known_split, split_match) if k == s or k[0] == '$') + matched_fields = sum(1 for k, s in zip(known_split, split_match) if k == s or k[0] == '$') + if matched_fields == len(known_split): + return True, '' + if highest_match < matched_fields: matched_fields = highest_match closest = list(known_split) From bc7985e8fb33b1138ee5ed83589404a4c2b46af8 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Mon, 23 Aug 2021 12:50:07 +0200 Subject: [PATCH 142/316] updated docs --- docs/Killswitches.md | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/docs/Killswitches.md b/docs/Killswitches.md index d0fa814b..d90607a1 100644 --- a/docs/Killswitches.md +++ b/docs/Killswitches.md @@ -177,3 +177,64 @@ The main killswitch file (`killswitches_v2.json`) is kept in the `releases` branch on the EDMC github repo. The file should NEVER be committed to any other repos. In the case that the killswitch file is found in other repos, the one in releases should always be taken as correct regardless of others. + +## In depth example + +In a hypothetical situation where we have released version 1.0.0 with a bug that +means `FSDJump` events are not correctly stripped of extraneous data, such as +the user specific `HomeSystem` field in the `Factions` object. + +The simplest way to go about this is to remove the field whenever the event is +passed to `eddn.py`s `journal_entry` function. + +`journal_entry` checks against both `plugins.eddn.journal` and +`plugins.eddn.journal.event.<eventname>`. As we just want to modify a single +events handling, we can use the latter form. + +The killswitch definition is as follows (this is just for this hypothetical, +it is not a full valid file, see below) + +```json +{ + "plugins.eddn.journal.event.FSDJump": { + "reason": "EDMC Does not correctly strip the user specific HomeSystem field from Factions", + "delete_fields": ["Factions.HomeSystem"] + } +} +``` + +This can be slotted into a full killswitch (using a modified version of the +example at the top of this file) + +```json +{ + "version": 2, + "last_updated": "23 August 2021", + "kill_switches": [{ + "version": "1.0.0", + "kills": { + "plugins.eddn.journal.event.FSDJump": { + "reason": "EDMC 1.0.0 Does not correctly strip the user specific HomeSystem field from Factions", + "delete_fields": ["Factions.HomeSystem"] + } + } + }] +} +Running the above example though `killswitch_test.py` returns: + +```plaintext +Kills matching version mask 1.0.0 + - plugins.eddn.journal.event.FSDJump + Reason specified is: 'EDMC Does not correctly strip the user specific HomeSystem field from Factions' + + The folowing changes are required for plugins.eddn.journal.event.FSDJump execution to continue + Deletes 1 fields: + - Factions.HomeSystem +``` + +Telling us that we have not made any typos, and that our killswitch matches the +expected version, and does the expected actions. + +Now that we're sure that everything is right, we can place this in the correct +location (see above for paths). Once there, EDMC instances will begin to behave +as expected, filtering out the field during EDDN processing. From a70c43098b3dd4af41601e5306fbcfd50b789c4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Aug 2021 17:03:20 +0000 Subject: [PATCH 143/316] build(deps): bump watchdog from 2.1.4 to 2.1.5 Bumps [watchdog](https://github.com/gorakhargosh/watchdog) from 2.1.4 to 2.1.5. - [Release notes](https://github.com/gorakhargosh/watchdog/releases) - [Changelog](https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst) - [Commits](https://github.com/gorakhargosh/watchdog/compare/v2.1.4...v2.1.5) --- updated-dependencies: - dependency-name: watchdog dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e7b9bd39..706d86d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ certifi==2021.5.30 requests==2.26.0 -watchdog==2.1.4 +watchdog==2.1.5 # Commented out because this doesn't package well with py2exe # infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep From 7339cc59ede1a5a9c96f3a0fab8c23d5879b3eb8 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 13:38:48 +0100 Subject: [PATCH 144/316] companion: Fix `class Session` docstring This is *not* only for the Frontier oAuth2 authn, it's also for the actual CAPI queries. --- companion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 08c92b7d..4bf77bc6 100644 --- a/companion.py +++ b/companion.py @@ -464,7 +464,7 @@ class Auth(object): class Session(object): - """Methods for handling an oAuth2 session.""" + """Methods for handling Frontier Auth and CAPI queries.""" STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) @@ -553,7 +553,7 @@ class Session(object): self.session.headers['User-Agent'] = USER_AGENT self.state = Session.STATE_OK - def query(self, endpoint: str) -> CAPIData: # noqa: CCR001 + def query(self, endpoint: str) -> CAPIData: # noqa: CCR001, C901 """Perform a query against the specified CAPI endpoint.""" logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: From 81b5d015fdeecfcf2236a61c433d4c2dac929668 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 13:45:38 +0100 Subject: [PATCH 145/316] Frontier Auth: Rename `start()` method to something more obvious --- EDMarketConnector.py | 4 ++-- companion.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index ab893b50..c58e21f1 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -754,7 +754,7 @@ class AppWindow(object): journal_lock.update_lock(self.w) # (Re-)install log monitoring - if not monitor.start(self.w): + if not monitor.start_frontier_auth(self.w): # LANG: ED Journal file location appears to be in error self.status['text'] = _('Error: Check E:D journal file location') @@ -1222,7 +1222,7 @@ class AppWindow(object): logger.info('Monitor: Disable WinSparkle automatic update checks') # Can't start dashboard monitoring - if not dashboard.start(self.w, monitor.started): + if not dashboard.start_frontier_auth(self.w, monitor.started): logger.info("Can't start Status monitoring") # Export loadout diff --git a/companion.py b/companion.py index 4bf77bc6..8f0633c1 100644 --- a/companion.py +++ b/companion.py @@ -516,7 +516,7 @@ class Session(object): if access_token: logger.debug('We have an access_token') self.auth = None - self.start(access_token) + self.start_frontier_auth(access_token) return True else: @@ -536,7 +536,7 @@ class Session(object): try: logger.debug('Trying authorize with payload from handler') - self.start(self.auth.authorize(protocolhandler.lastpayload)) # type: ignore + self.start_frontier_auth(self.auth.authorize(protocolhandler.lastpayload)) # type: ignore self.auth = None except Exception: @@ -545,7 +545,7 @@ class Session(object): self.auth = None raise # Bad thing happened - def start(self, access_token: str) -> None: + def start_frontier_auth(self, access_token: str) -> None: """Start an oAuth2 session.""" logger.debug('Starting session') self.session = requests.Session() From c01438f77ef0c775e5dd3156bd63b37c53b02808 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 13:48:22 +0100 Subject: [PATCH 146/316] companion: Move start_frontier_auth() above login() The former will be used by the latter, so this makes more sense when reading the code. In general I'm going to ensure there are two sections to this Session class: 1. Frontier Auth code. 2. CAPI query code. Yes, ideally I'd split it into two classes. But that kind of refactoring isn't for *this* branch. I just want things straight in my head for the "make CAPI queries run in a thread" changes. --- companion.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/companion.py b/companion.py index 8f0633c1..c96b84d4 100644 --- a/companion.py +++ b/companion.py @@ -476,6 +476,14 @@ class Session(object): self.auth: Optional[Auth] = None self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query + def start_frontier_auth(self, access_token: str) -> None: + """Start an oAuth2 session.""" + logger.debug('Starting session') + self.session = requests.Session() + self.session.headers['Authorization'] = f'Bearer {access_token}' + self.session.headers['User-Agent'] = USER_AGENT + self.state = Session.STATE_OK + def login(self, cmdr: str = None, is_beta: Union[None, bool] = None) -> bool: """ Attempt oAuth2 login. @@ -545,14 +553,6 @@ class Session(object): self.auth = None raise # Bad thing happened - def start_frontier_auth(self, access_token: str) -> None: - """Start an oAuth2 session.""" - logger.debug('Starting session') - self.session = requests.Session() - self.session.headers['Authorization'] = f'Bearer {access_token}' - self.session.headers['User-Agent'] = USER_AGENT - self.state = Session.STATE_OK - def query(self, endpoint: str) -> CAPIData: # noqa: CCR001, C901 """Perform a query against the specified CAPI endpoint.""" logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') From d0e139123881f00c773c60ff8f41354dab7f2f1c Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 13:52:33 +0100 Subject: [PATCH 147/316] companion: More function reordering, and comment-marking of sections The file has: 1a. class Session - Frontier Auth 1b. class Session - CAPI queries 1c. class Session - Utility functions (to do with CAPI query data, but not directly about *making* those queries. 2. Some non-class utility functions. --- companion.py | 54 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/companion.py b/companion.py index c96b84d4..28879cd7 100644 --- a/companion.py +++ b/companion.py @@ -476,6 +476,9 @@ class Session(object): self.auth: Optional[Auth] = None self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query + ###################################################################### + # Frontier Authorization + ###################################################################### def start_frontier_auth(self, access_token: str) -> None: """Start an oAuth2 session.""" logger.debug('Starting session') @@ -553,6 +556,29 @@ class Session(object): self.auth = None raise # Bad thing happened + def close(self) -> None: + """Close Frontier authorization session.""" + self.state = Session.STATE_INIT + if self.session: + try: + self.session.close() + + except Exception as e: + logger.debug('Frontier Auth: closing', exc_info=e) + + self.session = None + + def invalidate(self) -> None: + """Invalidate Frontier authorization credentials.""" + logger.debug('Forcing a full re-authentication') + # Force a full re-authentication + self.close() + Auth.invalidate(self.credentials['cmdr']) # type: ignore + ###################################################################### + + ###################################################################### + # CAPI queries + ###################################################################### def query(self, endpoint: str) -> CAPIData: # noqa: CCR001, C901 """Perform a query against the specified CAPI endpoint.""" logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') @@ -714,7 +740,11 @@ class Session(object): # WORKAROUND END return data + ###################################################################### + ###################################################################### + # Utility functions + ###################################################################### def suit_update(self, data: CAPIData) -> None: """ Update monitor.state suit data. @@ -752,25 +782,6 @@ class Session(object): else: monitor.state['SuitLoadouts'] = suit_loadouts - def close(self) -> None: - """Close CAPI authorization session.""" - self.state = Session.STATE_INIT - if self.session: - try: - self.session.close() - - except Exception as e: - logger.debug('Frontier CAPI Auth: closing', exc_info=e) - - self.session = None - - def invalidate(self) -> None: - """Invalidate oAuth2 credentials.""" - logger.debug('Forcing a full re-authentication') - # Force a full re-authentication - self.close() - Auth.invalidate(self.credentials['cmdr']) # type: ignore - # noinspection PyMethodMayBeStatic def dump(self, r: requests.Response) -> None: """Log, as error, status of requests.Response from CAPI request.""" @@ -802,8 +813,12 @@ class Session(object): indent=2, sort_keys=True, separators=(',', ': ')).encode('utf-8')) + ###################################################################### +###################################################################### +# Non-class utility functions +###################################################################### def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully simplified """ Fix up commodity names to English & miscellaneous anomalies fixes. @@ -940,6 +955,7 @@ def index_possibly_sparse_list(data: Union[Mapping[str, V], List[V]], key: int) else: raise ValueError(f'Unexpected data type {type(data)}') +###################################################################### # singleton From bfaf66d8735a14413edf904aefa25c6028db3708 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 14:06:31 +0100 Subject: [PATCH 148/316] EDMarketConnector: Correct back to monitor.start() Bad PyCharm Refactor ! No Cookie! --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index c58e21f1..d1dee989 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -754,7 +754,7 @@ class AppWindow(object): journal_lock.update_lock(self.w) # (Re-)install log monitoring - if not monitor.start_frontier_auth(self.w): + if not monitor.start(self.w): # LANG: ED Journal file location appears to be in error self.status['text'] = _('Error: Check E:D journal file location') From c5af0a339779c49f5de5f873d30dba4d47d9c736 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 14:07:29 +0100 Subject: [PATCH 149/316] companion: Create CAPI worker thread on startup Confirmed to also *not* block shutdown currently --- companion.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/companion.py b/companion.py index 28879cd7..69b6e63d 100644 --- a/companion.py +++ b/companion.py @@ -14,6 +14,7 @@ import json import numbers import os import random +import threading import time import urllib.parse import webbrowser @@ -476,6 +477,15 @@ class Session(object): self.auth: Optional[Auth] = None self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query + logger.info('Starting CAPI queries thread...') + self.capi_query_thread = threading.Thread( + target=self.capi_query_worker, + daemon=True, + name='CAPI worker' + ) + self.capi_query_thread.start() + logger.info('Done') + ###################################################################### # Frontier Authorization ###################################################################### @@ -579,6 +589,18 @@ class Session(object): ###################################################################### # CAPI queries ###################################################################### + def capi_query_enqueue(self, endpoint: str) -> None: + """Request the worker thread perform a given endpoint query.""" + ... + + def capi_query_worker(self, ): + """Worker thread that performs actual CAPI queries.""" + logger.info('CAPI worker thread starting') + while True: + time.sleep(1) + + logger.info('CAPI worker thread DONE') + def query(self, endpoint: str) -> CAPIData: # noqa: CCR001, C901 """Perform a query against the specified CAPI endpoint.""" logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') From 9661d770cb6aa33525583854d66193c81510cfbc Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 14:20:32 +0100 Subject: [PATCH 150/316] companion: Implement, and use, asking CAPI thread to close down --- EDMarketConnector.py | 4 ++++ companion.py | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index d1dee989..0e6cc95c 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1577,6 +1577,10 @@ class AppWindow(object): logger.info('Unregistering hotkey manager...') hotkeymgr.unregister() + # Now the CAPI query thread + logger.info('Closing CAPI query thread...') + companion.session.capi_query_close_worker() + # Now the main programmatic input methods logger.info('Closing dashboard...') dashboard.close() diff --git a/companion.py b/companion.py index 69b6e63d..970f3e64 100644 --- a/companion.py +++ b/companion.py @@ -21,6 +21,7 @@ import webbrowser from builtins import object, range, str from email.utils import parsedate from os.path import join +from queue import Queue from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, OrderedDict, TypeVar, Union import requests @@ -478,6 +479,7 @@ class Session(object): self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query logger.info('Starting CAPI queries thread...') + self.capi_query_queue: Queue = Queue() self.capi_query_thread = threading.Thread( target=self.capi_query_worker, daemon=True, @@ -596,11 +598,22 @@ class Session(object): def capi_query_worker(self, ): """Worker thread that performs actual CAPI queries.""" logger.info('CAPI worker thread starting') + while True: + query: Optional[str] = self.capi_query_queue.get() + if not query: + logger.info('Empty queue message, exiting...') + break + + logger.trace_if('capi.worker', f'Processing query: {query}') time.sleep(1) logger.info('CAPI worker thread DONE') + def capi_query_close_worker(self) -> None: + """Ask the CAPI query thread to finish.""" + self.capi_query_queue.put(None) + def query(self, endpoint: str) -> CAPIData: # noqa: CCR001, C901 """Perform a query against the specified CAPI endpoint.""" logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') From 11916f1387fa394625ccf4df04ae613fb475f368 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 14:23:01 +0100 Subject: [PATCH 151/316] companion; Dummy CAPI worker enqueue to test code This properly triggered the logging in the worker thread --- companion.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/companion.py b/companion.py index 970f3e64..66d3440f 100644 --- a/companion.py +++ b/companion.py @@ -591,10 +591,6 @@ class Session(object): ###################################################################### # CAPI queries ###################################################################### - def capi_query_enqueue(self, endpoint: str) -> None: - """Request the worker thread perform a given endpoint query.""" - ... - def capi_query_worker(self, ): """Worker thread that performs actual CAPI queries.""" logger.info('CAPI worker thread starting') @@ -630,6 +626,7 @@ class Session(object): raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') try: + self.capi_query_queue.put(endpoint) r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore except requests.ConnectionError as e: From 9c079c190d62a28b1ef514ee2f92aa12fd27db8d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 14:32:08 +0100 Subject: [PATCH 152/316] CAPI: Set up AppWindow handling of <<CAPIResponse>> Tk event --- EDMarketConnector.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 0e6cc95c..447baea7 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -648,7 +648,8 @@ class AppWindow(object): self.w.bind('<FocusOut>', self.onleave) # Special handling for transparency self.w.bind('<Return>', self.getandsend) self.w.bind('<KP_Enter>', self.getandsend) - self.w.bind_all('<<Invoke>>', self.getandsend) # Hotkey monitoring + self.w.bind_all('<<Invoke>>', self.getandsend) # Ask for CAPI queries to be performed + self.w.bind_all('<<CAPIResponse>>', self.capi_handle_response) self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring self.w.bind_all('<<DashboardEvent>>', self.dashboard_event) # Dashboard monitoring self.w.bind_all('<<PluginError>>', self.plugin_error) # Statusbar @@ -1089,6 +1090,10 @@ class AppWindow(object): self.suit_show_if_set() self.cooldown() + def capi_handle_response(self, event=None): + """Handle the resulting data from a CAPI query.""" + ... + def journal_event(self, event): # noqa: C901, CCR001 # Currently not easily broken up. """ Handle a Journal event passed through event queue from monitor.py. From e79d219657a89dbc1b5e944683e3ad5d323a74dc Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 14:33:46 +0100 Subject: [PATCH 153/316] CAPI: Rename AppWindow.getandsend() to be more obvious --- EDMarketConnector.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 447baea7..df80721d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -485,8 +485,8 @@ class AppWindow(object): theme.register_alternate((self.button, self.theme_button, self.theme_button), {'row': ui_row, 'columnspan': 2, 'sticky': tk.NSEW}) self.status.grid(columnspan=2, sticky=tk.EW) - self.button.bind('<Button-1>', self.getandsend) - theme.button_bind(self.theme_button, self.getandsend) + self.button.bind('<Button-1>', self.capi_request_data) + theme.button_bind(self.theme_button, self.capi_request_data) for child in frame.winfo_children(): child.grid_configure(padx=self.PADX, pady=(platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0) @@ -646,9 +646,9 @@ class AppWindow(object): self.w.bind('<FocusIn>', self.onenter) # Special handling for transparency self.w.bind('<Leave>', self.onleave) # Special handling for transparency self.w.bind('<FocusOut>', self.onleave) # Special handling for transparency - self.w.bind('<Return>', self.getandsend) - self.w.bind('<KP_Enter>', self.getandsend) - self.w.bind_all('<<Invoke>>', self.getandsend) # Ask for CAPI queries to be performed + self.w.bind('<Return>', self.capi_request_data) + self.w.bind('<KP_Enter>', self.capi_request_data) + self.w.bind_all('<<Invoke>>', self.capi_request_data) # Ask for CAPI queries to be performed self.w.bind_all('<<CAPIResponse>>', self.capi_handle_response) self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring self.w.bind_all('<<DashboardEvent>>', self.dashboard_event) # Dashboard monitoring @@ -887,7 +887,7 @@ class AppWindow(object): return True - def getandsend(self, event=None, retrying: bool = False): # noqa: C901, CCR001 + def capi_request_data(self, event=None, retrying: bool = False): # noqa: C901, CCR001 """ Perform CAPI data retrieval and associated actions. @@ -1060,7 +1060,7 @@ class AppWindow(object): else: # Retry once if Companion server is unresponsive - self.w.after(int(SERVER_RETRY * 1000), lambda: self.getandsend(event, True)) + self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event, True)) return # early exit to avoid starting cooldown count except companion.CmdrError as e: # Companion API return doesn't match Journal @@ -1269,7 +1269,7 @@ class AppWindow(object): auto_update = True if auto_update: - self.w.after(int(SERVER_RETRY * 1000), self.getandsend) + self.w.after(int(SERVER_RETRY * 1000), self.capi_request_data) if entry['event'] == 'ShutDown': # Enable WinSparkle automatic update checks From 2fc1568bf77886ad27d11ae0fbd2500e5fd60433 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 14:41:29 +0100 Subject: [PATCH 154/316] CAPI: Move CAPI response processing into AppWindow.capi_handle_response() --- EDMarketConnector.py | 69 ++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index df80721d..fb16422d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -926,11 +926,14 @@ class AppWindow(object): self.button['state'] = self.theme_button['state'] = tk.DISABLED self.w.update_idletasks() - try: - querytime = int(time()) - data = companion.session.station() - config.set('querytime', querytime) + querytime = int(time()) + data = companion.session.station() + config.set('querytime', querytime) + + def capi_handle_response(self, event=None): + """Handle the resulting data from a CAPI query.""" + data = ... # Validation if 'commander' not in data: # This can happen with EGS Auth if no commander created yet @@ -1051,33 +1054,33 @@ class AppWindow(object): self.holdofftime = querytime + companion.holdoff - # Companion API problem - except companion.ServerLagging as e: - err = str(e) - if retrying: - self.status['text'] = err - play_bad = True - - else: - # Retry once if Companion server is unresponsive - self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event, True)) - return # early exit to avoid starting cooldown count - - except companion.CmdrError as e: # Companion API return doesn't match Journal - err = self.status['text'] = str(e) - play_bad = True - companion.session.invalidate() - self.login() - - except companion.ServerConnectionError as e: - logger.warning(f'Exception while contacting server: {e}') - err = self.status['text'] = str(e) - play_bad = True - - except Exception as e: # Including CredentialsError, ServerError - logger.debug('"other" exception', exc_info=e) - err = self.status['text'] = str(e) - play_bad = True +# # Companion API problem +# except companion.ServerLagging as e: +# err = str(e) +# if retrying: +# self.status['text'] = err +# play_bad = True +# +# else: +# # Retry once if Companion server is unresponsive +# self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event, True)) +# return # early exit to avoid starting cooldown count +# +# except companion.CmdrError as e: # Companion API return doesn't match Journal +# err = self.status['text'] = str(e) +# play_bad = True +# companion.session.invalidate() +# self.login() +# +# except companion.ServerConnectionError as e: +# logger.warning(f'Exception while contacting server: {e}') +# err = self.status['text'] = str(e) +# play_bad = True +# +# except Exception as e: # Including CredentialsError, ServerError +# logger.debug('"other" exception', exc_info=e) +# err = self.status['text'] = str(e) +# play_bad = True if not err: # not self.status['text']: # no errors # LANG: Time when we last obtained Frontier CAPI data @@ -1090,10 +1093,6 @@ class AppWindow(object): self.suit_show_if_set() self.cooldown() - def capi_handle_response(self, event=None): - """Handle the resulting data from a CAPI query.""" - ... - def journal_event(self, event): # noqa: C901, CCR001 # Currently not easily broken up. """ Handle a Journal event passed through event queue from monitor.py. From 655c7ea1ca9b677013d372efedbec6f279ac983f Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 15:37:55 +0100 Subject: [PATCH 155/316] CAPI: Some progress in using a Queue to signal result/error Due to main app being Tk we can't just use Python async functionality. So instead we have a class to hold a message and optional exception. And instance of that goes into a queue. The reading of that in the main thread is triggered by sending a Tk event. Much more to come. --- EDMarketConnector.py | 210 +++++++++++++++++++++++-------------------- companion.py | 102 +++++++++++++-------- 2 files changed, 179 insertions(+), 133 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index fb16422d..3b06bfdf 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -7,6 +7,7 @@ import html import json import locale import pathlib +import queue import re import sys # import threading @@ -14,6 +15,7 @@ import webbrowser from builtins import object, str from os import chdir, environ from os.path import dirname, join +from queue import Queue from sys import platform from time import localtime, strftime, time from typing import TYPE_CHECKING, Optional, Tuple @@ -384,6 +386,8 @@ class AppWindow(object): def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out self.holdofftime = config.get_int('querytime', default=0) + companion.holdoff + self.capi_response_queue: Queue = Queue() + companion.session.set_capi_response_queue(self.capi_response_queue) self.w = master self.w.title(applongname) @@ -933,126 +937,138 @@ class AppWindow(object): def capi_handle_response(self, event=None): """Handle the resulting data from a CAPI query.""" - data = ... - # Validation - if 'commander' not in data: - # This can happen with EGS Auth if no commander created yet - # LANG: No data was returned for the commander from the Frontier CAPI - err = self.status['text'] = _('CAPI: No commander data returned') + try: + data = self.capi_response_queue.get(block=False) - elif not data.get('commander', {}).get('name'): - # LANG: We didn't have the commander name when we should have - err = self.status['text'] = _("Who are you?!") # Shouldn't happen + except queue.Empty: + logger.error('There was no response in the queue!') + # TODO: Set status text + return - elif (not data.get('lastSystem', {}).get('name') - or (data['commander'].get('docked') - and not data.get('lastStarport', {}).get('name'))): - # LANG: We don't know where the commander is, when we should - err = self.status['text'] = _("Where are you?!") # Shouldn't happen + else: + if isinstance(data, companion.CAPIFailedRequest): + logger.trace_if('capi.worker', f'Failed Request: {data.message}') + raise data.exception - elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): - # LANG: We don't know what ship the commander is in, when we should - err = self.status['text'] = _("What are you flying?!") # Shouldn't happen + # Validation + if 'commander' not in data: + # This can happen with EGS Auth if no commander created yet + # LANG: No data was returned for the commander from the Frontier CAPI + err = self.status['text'] = _('CAPI: No commander data returned') - elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: - # Companion API Commander doesn't match Journal - raise companion.CmdrError() + elif not data.get('commander', {}).get('name'): + # LANG: We didn't have the commander name when we should have + err = self.status['text'] = _("Who are you?!") # Shouldn't happen - elif auto_update and not monitor.state['OnFoot'] and not data['commander'].get('docked'): - # auto update is only when just docked - logger.warning(f"{auto_update!r} and not {monitor.state['OnFoot']!r} and " - f"not {data['commander'].get('docked')!r}") - raise companion.ServerLagging() + elif (not data.get('lastSystem', {}).get('name') + or (data['commander'].get('docked') + and not data.get('lastStarport', {}).get('name'))): + # LANG: We don't know where the commander is, when we should + err = self.status['text'] = _("Where are you?!") # Shouldn't happen - elif data['lastSystem']['name'] != monitor.system: - # CAPI system must match last journal one - logger.warning(f"{data['lastSystem']['name']!r} != {monitor.system!r}") - raise companion.ServerLagging() + elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): + # LANG: We don't know what ship the commander is in, when we should + err = self.status['text'] = _("What are you flying?!") # Shouldn't happen - elif data['lastStarport']['name'] != monitor.station: - if monitor.state['OnFoot'] and monitor.station: - logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " - f"{monitor.state['OnFoot']!r} and {monitor.station!r}") - raise companion.ServerLagging() + elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: + # Companion API Commander doesn't match Journal + raise companion.CmdrError() - else: - last_station = None - if data['commander']['docked']: - last_station = data['lastStarport']['name'] + elif auto_update and not monitor.state['OnFoot'] and not data['commander'].get('docked'): + # auto update is only when just docked + logger.warning(f"{auto_update!r} and not {monitor.state['OnFoot']!r} and " + f"not {data['commander'].get('docked')!r}") + raise companion.ServerLagging() - if monitor.station is None: - # Likely (re-)Embarked on ship docked at an EDO settlement. - # Both Disembark and Embark have `"Onstation": false` in Journal. - # So there's nothing to tell us which settlement we're (still, - # or now, if we came here in Apex and then recalled ship) docked at. - logger.debug("monitor.station is None - so EDO settlement?") - raise companion.NoMonitorStation() + elif data['lastSystem']['name'] != monitor.system: + # CAPI system must match last journal one + logger.warning(f"{data['lastSystem']['name']!r} != {monitor.system!r}") + raise companion.ServerLagging() - if last_station != monitor.station: - # CAPI lastStarport must match - logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " - f"{last_station!r} != {monitor.station!r}") - raise companion.ServerLagging() - - self.holdofftime = querytime + companion.holdoff - - elif not monitor.state['OnFoot'] and data['ship']['id'] != monitor.state['ShipID']: - # CAPI ship must match - logger.warning(f"not {monitor.state['OnFoot']!r} and " - f"{data['ship']['id']!r} != {monitor.state['ShipID']!r}") - raise companion.ServerLagging() - - elif not monitor.state['OnFoot'] and data['ship']['name'].lower() != monitor.state['ShipType']: - # CAPI ship type must match - logger.warning(f"not {monitor.state['OnFoot']!r} and " - f"{data['ship']['name'].lower()!r} != {monitor.state['ShipType']!r}") + elif data['lastStarport']['name'] != monitor.station: + if monitor.state['OnFoot'] and monitor.station: + logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " + f"{monitor.state['OnFoot']!r} and {monitor.station!r}") raise companion.ServerLagging() else: - if __debug__: # Recording - companion.session.dump_capi_data(data) + last_station = None + if data['commander']['docked']: + last_station = data['lastStarport']['name'] - if not monitor.state['ShipType']: # Started game in SRV or fighter - self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name']) - monitor.state['ShipID'] = data['ship']['id'] - monitor.state['ShipType'] = data['ship']['name'].lower() + if monitor.station is None: + # Likely (re-)Embarked on ship docked at an EDO settlement. + # Both Disembark and Embark have `"Onstation": false` in Journal. + # So there's nothing to tell us which settlement we're (still, + # or now, if we came here in Apex and then recalled ship) docked at. + logger.debug("monitor.station is None - so EDO settlement?") + raise companion.NoMonitorStation() - if not monitor.state['Modules']: - self.ship.configure(state=tk.DISABLED) + if last_station != monitor.station: + # CAPI lastStarport must match + logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " + f"{last_station!r} != {monitor.station!r}") + raise companion.ServerLagging() - # We might have disabled this in the conditional above. - if monitor.state['Modules']: - self.ship.configure(state=True) + self.holdofftime = querytime + companion.holdoff - if monitor.state.get('SuitCurrent') is not None: - if (loadout := data.get('loadout')) is not None: - if (suit := loadout.get('suit')) is not None: - if (suitname := suit.get('edmcName')) is not None: - # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there - loadout_name = index_possibly_sparse_list( - data['loadouts'], loadout['loadoutSlotId'] - )['name'] + elif not monitor.state['OnFoot'] and data['ship']['id'] != monitor.state['ShipID']: + # CAPI ship must match + logger.warning(f"not {monitor.state['OnFoot']!r} and " + f"{data['ship']['id']!r} != {monitor.state['ShipID']!r}") + raise companion.ServerLagging() - self.suit['text'] = f'{suitname} ({loadout_name})' + elif not monitor.state['OnFoot'] and data['ship']['name'].lower() != monitor.state['ShipType']: + # CAPI ship type must match + logger.warning(f"not {monitor.state['OnFoot']!r} and " + f"{data['ship']['name'].lower()!r} != {monitor.state['ShipType']!r}") + raise companion.ServerLagging() - self.suit_show_if_set() + else: + if __debug__: # Recording + companion.session.dump_capi_data(data) - if data['commander'].get('credits') is not None: - monitor.state['Credits'] = data['commander']['credits'] - monitor.state['Loan'] = data['commander'].get('debt', 0) + if not monitor.state['ShipType']: # Started game in SRV or fighter + self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name']) + monitor.state['ShipID'] = data['ship']['id'] + monitor.state['ShipType'] = data['ship']['name'].lower() - # stuff we can do when not docked - err = plug.notify_newdata(data, monitor.is_beta) - self.status['text'] = err and err or '' - if err: - play_bad = True + if not monitor.state['Modules']: + self.ship.configure(state=tk.DISABLED) - # Export market data - if not self.export_market_data(data): - err = 'Error: Exporting Market data' - play_bad = True + # We might have disabled this in the conditional above. + if monitor.state['Modules']: + self.ship.configure(state=True) - self.holdofftime = querytime + companion.holdoff + if monitor.state.get('SuitCurrent') is not None: + if (loadout := data.get('loadout')) is not None: + if (suit := loadout.get('suit')) is not None: + if (suitname := suit.get('edmcName')) is not None: + # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there + loadout_name = index_possibly_sparse_list( + data['loadouts'], loadout['loadoutSlotId'] + )['name'] + + self.suit['text'] = f'{suitname} ({loadout_name})' + + self.suit_show_if_set() + + if data['commander'].get('credits') is not None: + monitor.state['Credits'] = data['commander']['credits'] + monitor.state['Loan'] = data['commander'].get('debt', 0) + + # stuff we can do when not docked + err = plug.notify_newdata(data, monitor.is_beta) + self.status['text'] = err and err or '' + if err: + play_bad = True + + # Export market data + if not self.export_market_data(data): + err = 'Error: Exporting Market data' + play_bad = True + + self.holdofftime = querytime + companion.holdoff # # Companion API problem # except companion.ServerLagging as e: diff --git a/companion.py b/companion.py index 66d3440f..d1177a31 100644 --- a/companion.py +++ b/companion.py @@ -15,6 +15,7 @@ import numbers import os import random import threading +import tkinter as tk import time import urllib.parse import webbrowser @@ -465,6 +466,13 @@ class Auth(object): return base64.urlsafe_b64encode(text).decode().replace('=', '') +class CAPIFailedRequest(): + """CAPI failed query error class.""" + + def __init__(self, message, exception=None): + self.message = message + self.exception = exception + class Session(object): """Methods for handling Frontier Auth and CAPI queries.""" @@ -477,8 +485,10 @@ class Session(object): self.session: Optional[requests.Session] = None self.auth: Optional[Auth] = None self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query + self.tk_master: Optional[tk.Tk] = None logger.info('Starting CAPI queries thread...') + self.capi_response_queue: Queue self.capi_query_queue: Queue = Queue() self.capi_query_thread = threading.Thread( target=self.capi_query_worker, @@ -488,6 +498,12 @@ class Session(object): self.capi_query_thread.start() logger.info('Done') + def set_capi_response_queue(self, capi_response_queue: Queue) -> None: + self.capi_response_queue = capi_response_queue + + def set_tk_master(self, master: tk.Tk) -> None: + self.tk_master = master + ###################################################################### # Frontier Authorization ###################################################################### @@ -591,18 +607,61 @@ class Session(object): ###################################################################### # CAPI queries ###################################################################### - def capi_query_worker(self, ): + def capi_query_worker(self): """Worker thread that performs actual CAPI queries.""" logger.info('CAPI worker thread starting') while True: - query: Optional[str] = self.capi_query_queue.get() - if not query: + endpoint: Optional[str] = self.capi_query_queue.get() + if not endpoint: logger.info('Empty queue message, exiting...') break - logger.trace_if('capi.worker', f'Processing query: {query}') - time.sleep(1) + logger.trace_if('capi.worker', f'Processing query: {endpoint}') + # XXX + self.capi_response_queue.put( + CAPIFailedRequest(f'Unable to connect to endpoint {endpoint}') + ) + self.tk_master.event_generate('<<CAPIResponse>>') + continue + # XXX + try: + r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore + r.raise_for_status() # Typically 403 "Forbidden" on token expiry + data = CAPIData(r.json(), endpoint) # May also fail here if token expired since response is empty + + except requests.ConnectionError as e: + logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {endpoint})') + self.capi_response_queue.put( + CAPIFailedRequest(f'Unable to connect to endpoint {endpoint}', exception=e) + ) + continue + # raise ServerConnectionError(f'Unable to connect to endpoint {endpoint}') from e + + except Exception as e: + logger.debug('Attempting GET', exc_info=e) + # LANG: Frontier CAPI data retrieval failed + # raise ServerError(f'{_("Frontier CAPI query failure")}: {endpoint}') from e + self.capi_response_queue.put( + CAPIFailedRequest(f'Frontier CAPI query failure: {endpoint}', exception=e) + ) + continue + + if r.url.startswith(SERVER_AUTH): + logger.info('Redirected back to Auth Server') + # Redirected back to Auth server - force full re-authentication + self.dump(r) + self.invalidate() + self.retrying = False + self.login() + raise CredentialsError() + + elif 500 <= r.status_code < 600: + # Server error. Typically 500 "Internal Server Error" if server is down + logger.debug('500 status back from CAPI') + self.dump(r) + # LANG: Frontier CAPI data retrieval failed with 5XX code + raise ServerError(f'{_("Frontier CAPI server error")}: {r.status_code}') logger.info('CAPI worker thread DONE') @@ -625,38 +684,9 @@ class Session(object): if conf_module.capi_pretend_down: raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') + self.capi_query_queue.put(endpoint) try: - self.capi_query_queue.put(endpoint) - r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore - - except requests.ConnectionError as e: - logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {endpoint})') - raise ServerConnectionError(f'Unable to connect to endpoint {endpoint}') from e - - except Exception as e: - logger.debug('Attempting GET', exc_info=e) - # LANG: Frontier CAPI data retrieval failed - raise ServerError(f'{_("Frontier CAPI query failure")}: {endpoint}') from e - - if r.url.startswith(SERVER_AUTH): - logger.info('Redirected back to Auth Server') - # Redirected back to Auth server - force full re-authentication - self.dump(r) - self.invalidate() - self.retrying = False - self.login() - raise CredentialsError() - - elif 500 <= r.status_code < 600: - # Server error. Typically 500 "Internal Server Error" if server is down - logger.debug('500 status back from CAPI') - self.dump(r) - # LANG: Frontier CAPI data retrieval failed with 5XX code - raise ServerError(f'{_("Frontier CAPI server error")}: {r.status_code}') - - try: - r.raise_for_status() # Typically 403 "Forbidden" on token expiry - data = CAPIData(r.json(), endpoint) # May also fail here if token expired since response is empty + ... except (requests.HTTPError, ValueError) as e: logger.exception('Frontier CAPI Auth: GET ') From 407bc9f53a3034346b7657cba0b76fe7914ee613 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 15:42:13 +0100 Subject: [PATCH 156/316] CAPI: Set companion.Session.tk_master ASAP from AppWindow --- EDMarketConnector.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 3b06bfdf..9dba289b 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -394,6 +394,9 @@ class AppWindow(object): self.w.rowconfigure(0, weight=1) self.w.columnconfigure(0, weight=1) + # companion needs to be able to send <<CAPIResponse>> events + companion.session.set_tk_master(self.w) + self.prefsdialog = None # if platform == 'win32': From 7f362be740a56599d08943a946d39bd287c7ffac Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 15:50:25 +0100 Subject: [PATCH 157/316] CAPI: Reinstate/format post-query exceptions/etc in AppWindow --- EDMarketConnector.py | 260 ++++++++++++++++++++++--------------------- 1 file changed, 131 insertions(+), 129 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 9dba289b..ca30c484 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -942,164 +942,166 @@ class AppWindow(object): """Handle the resulting data from a CAPI query.""" try: data = self.capi_response_queue.get(block=False) - - except queue.Empty: - logger.error('There was no response in the queue!') - # TODO: Set status text - return - - else: if isinstance(data, companion.CAPIFailedRequest): logger.trace_if('capi.worker', f'Failed Request: {data.message}') - raise data.exception + if data.exception: + raise data.exception - # Validation - if 'commander' not in data: - # This can happen with EGS Auth if no commander created yet - # LANG: No data was returned for the commander from the Frontier CAPI - err = self.status['text'] = _('CAPI: No commander data returned') + else: + raise ValueError(data.message) - elif not data.get('commander', {}).get('name'): - # LANG: We didn't have the commander name when we should have - err = self.status['text'] = _("Who are you?!") # Shouldn't happen + # Validation + if 'commander' not in data: + # This can happen with EGS Auth if no commander created yet + # LANG: No data was returned for the commander from the Frontier CAPI + err = self.status['text'] = _('CAPI: No commander data returned') - elif (not data.get('lastSystem', {}).get('name') - or (data['commander'].get('docked') - and not data.get('lastStarport', {}).get('name'))): - # LANG: We don't know where the commander is, when we should - err = self.status['text'] = _("Where are you?!") # Shouldn't happen + elif not data.get('commander', {}).get('name'): + # LANG: We didn't have the commander name when we should have + err = self.status['text'] = _("Who are you?!") # Shouldn't happen - elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): - # LANG: We don't know what ship the commander is in, when we should - err = self.status['text'] = _("What are you flying?!") # Shouldn't happen + elif (not data.get('lastSystem', {}).get('name') + or (data['commander'].get('docked') + and not data.get('lastStarport', {}).get('name'))): + # LANG: We don't know where the commander is, when we should + err = self.status['text'] = _("Where are you?!") # Shouldn't happen - elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: - # Companion API Commander doesn't match Journal - raise companion.CmdrError() + elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): + # LANG: We don't know what ship the commander is in, when we should + err = self.status['text'] = _("What are you flying?!") # Shouldn't happen - elif auto_update and not monitor.state['OnFoot'] and not data['commander'].get('docked'): - # auto update is only when just docked - logger.warning(f"{auto_update!r} and not {monitor.state['OnFoot']!r} and " - f"not {data['commander'].get('docked')!r}") - raise companion.ServerLagging() + elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: + # Companion API Commander doesn't match Journal + raise companion.CmdrError() - elif data['lastSystem']['name'] != monitor.system: - # CAPI system must match last journal one - logger.warning(f"{data['lastSystem']['name']!r} != {monitor.system!r}") - raise companion.ServerLagging() + elif auto_update and not monitor.state['OnFoot'] and not data['commander'].get('docked'): + # auto update is only when just docked + logger.warning(f"{auto_update!r} and not {monitor.state['OnFoot']!r} and " + f"not {data['commander'].get('docked')!r}") + raise companion.ServerLagging() - elif data['lastStarport']['name'] != monitor.station: - if monitor.state['OnFoot'] and monitor.station: - logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " - f"{monitor.state['OnFoot']!r} and {monitor.station!r}") + elif data['lastSystem']['name'] != monitor.system: + # CAPI system must match last journal one + logger.warning(f"{data['lastSystem']['name']!r} != {monitor.system!r}") + raise companion.ServerLagging() + + elif data['lastStarport']['name'] != monitor.station: + if monitor.state['OnFoot'] and monitor.station: + logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " + f"{monitor.state['OnFoot']!r} and {monitor.station!r}") + raise companion.ServerLagging() + + else: + last_station = None + if data['commander']['docked']: + last_station = data['lastStarport']['name'] + + if monitor.station is None: + # Likely (re-)Embarked on ship docked at an EDO settlement. + # Both Disembark and Embark have `"Onstation": false` in Journal. + # So there's nothing to tell us which settlement we're (still, + # or now, if we came here in Apex and then recalled ship) docked at. + logger.debug("monitor.station is None - so EDO settlement?") + raise companion.NoMonitorStation() + + if last_station != monitor.station: + # CAPI lastStarport must match + logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " + f"{last_station!r} != {monitor.station!r}") + raise companion.ServerLagging() + + self.holdofftime = querytime + companion.holdoff + + elif not monitor.state['OnFoot'] and data['ship']['id'] != monitor.state['ShipID']: + # CAPI ship must match + logger.warning(f"not {monitor.state['OnFoot']!r} and " + f"{data['ship']['id']!r} != {monitor.state['ShipID']!r}") + raise companion.ServerLagging() + + elif not monitor.state['OnFoot'] and data['ship']['name'].lower() != monitor.state['ShipType']: + # CAPI ship type must match + logger.warning(f"not {monitor.state['OnFoot']!r} and " + f"{data['ship']['name'].lower()!r} != {monitor.state['ShipType']!r}") raise companion.ServerLagging() else: - last_station = None - if data['commander']['docked']: - last_station = data['lastStarport']['name'] + if __debug__: # Recording + companion.session.dump_capi_data(data) - if monitor.station is None: - # Likely (re-)Embarked on ship docked at an EDO settlement. - # Both Disembark and Embark have `"Onstation": false` in Journal. - # So there's nothing to tell us which settlement we're (still, - # or now, if we came here in Apex and then recalled ship) docked at. - logger.debug("monitor.station is None - so EDO settlement?") - raise companion.NoMonitorStation() + if not monitor.state['ShipType']: # Started game in SRV or fighter + self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name']) + monitor.state['ShipID'] = data['ship']['id'] + monitor.state['ShipType'] = data['ship']['name'].lower() - if last_station != monitor.station: - # CAPI lastStarport must match - logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " - f"{last_station!r} != {monitor.station!r}") - raise companion.ServerLagging() + if not monitor.state['Modules']: + self.ship.configure(state=tk.DISABLED) - self.holdofftime = querytime + companion.holdoff + # We might have disabled this in the conditional above. + if monitor.state['Modules']: + self.ship.configure(state=True) - elif not monitor.state['OnFoot'] and data['ship']['id'] != monitor.state['ShipID']: - # CAPI ship must match - logger.warning(f"not {monitor.state['OnFoot']!r} and " - f"{data['ship']['id']!r} != {monitor.state['ShipID']!r}") - raise companion.ServerLagging() + if monitor.state.get('SuitCurrent') is not None: + if (loadout := data.get('loadout')) is not None: + if (suit := loadout.get('suit')) is not None: + if (suitname := suit.get('edmcName')) is not None: + # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there + loadout_name = index_possibly_sparse_list( + data['loadouts'], loadout['loadoutSlotId'] + )['name'] - elif not monitor.state['OnFoot'] and data['ship']['name'].lower() != monitor.state['ShipType']: - # CAPI ship type must match - logger.warning(f"not {monitor.state['OnFoot']!r} and " - f"{data['ship']['name'].lower()!r} != {monitor.state['ShipType']!r}") - raise companion.ServerLagging() + self.suit['text'] = f'{suitname} ({loadout_name})' - else: - if __debug__: # Recording - companion.session.dump_capi_data(data) + self.suit_show_if_set() - if not monitor.state['ShipType']: # Started game in SRV or fighter - self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name']) - monitor.state['ShipID'] = data['ship']['id'] - monitor.state['ShipType'] = data['ship']['name'].lower() + if data['commander'].get('credits') is not None: + monitor.state['Credits'] = data['commander']['credits'] + monitor.state['Loan'] = data['commander'].get('debt', 0) - if not monitor.state['Modules']: - self.ship.configure(state=tk.DISABLED) + # stuff we can do when not docked + err = plug.notify_newdata(data, monitor.is_beta) + self.status['text'] = err and err or '' + if err: + play_bad = True - # We might have disabled this in the conditional above. - if monitor.state['Modules']: - self.ship.configure(state=True) + # Export market data + if not self.export_market_data(data): + err = 'Error: Exporting Market data' + play_bad = True - if monitor.state.get('SuitCurrent') is not None: - if (loadout := data.get('loadout')) is not None: - if (suit := loadout.get('suit')) is not None: - if (suitname := suit.get('edmcName')) is not None: - # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there - loadout_name = index_possibly_sparse_list( - data['loadouts'], loadout['loadoutSlotId'] - )['name'] + self.holdofftime = querytime + companion.holdoff - self.suit['text'] = f'{suitname} ({loadout_name})' + except queue.Empty: + logger.error('There was no response in the queue!') + # TODO: Set status text + return - self.suit_show_if_set() - - if data['commander'].get('credits') is not None: - monitor.state['Credits'] = data['commander']['credits'] - monitor.state['Loan'] = data['commander'].get('debt', 0) - - # stuff we can do when not docked - err = plug.notify_newdata(data, monitor.is_beta) - self.status['text'] = err and err or '' - if err: + # Companion API problem + except companion.ServerLagging as e: + err = str(e) + if retrying: + self.status['text'] = err play_bad = True - # Export market data - if not self.export_market_data(data): - err = 'Error: Exporting Market data' - play_bad = True + else: + # Retry once if Companion server is unresponsive + self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event, True)) + return # early exit to avoid starting cooldown count - self.holdofftime = querytime + companion.holdoff + except companion.CmdrError as e: # Companion API return doesn't match Journal + err = self.status['text'] = str(e) + play_bad = True + companion.session.invalidate() + self.login() -# # Companion API problem -# except companion.ServerLagging as e: -# err = str(e) -# if retrying: -# self.status['text'] = err -# play_bad = True -# -# else: -# # Retry once if Companion server is unresponsive -# self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event, True)) -# return # early exit to avoid starting cooldown count -# -# except companion.CmdrError as e: # Companion API return doesn't match Journal -# err = self.status['text'] = str(e) -# play_bad = True -# companion.session.invalidate() -# self.login() -# -# except companion.ServerConnectionError as e: -# logger.warning(f'Exception while contacting server: {e}') -# err = self.status['text'] = str(e) -# play_bad = True -# -# except Exception as e: # Including CredentialsError, ServerError -# logger.debug('"other" exception', exc_info=e) -# err = self.status['text'] = str(e) -# play_bad = True + except companion.ServerConnectionError as e: + logger.warning(f'Exception while contacting server: {e}') + err = self.status['text'] = str(e) + play_bad = True + + except Exception as e: # Including CredentialsError, ServerError + logger.debug('"other" exception', exc_info=e) + err = self.status['text'] = str(e) + play_bad = True if not err: # not self.status['text']: # no errors # LANG: Time when we last obtained Frontier CAPI data From ce02cfdf2d6cce2bf45619dc620b1028442668d2 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 15:57:24 +0100 Subject: [PATCH 158/316] CAPI: Remove "test failure propogration" forced error --- companion.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/companion.py b/companion.py index d1177a31..7c67a892 100644 --- a/companion.py +++ b/companion.py @@ -618,13 +618,6 @@ class Session(object): break logger.trace_if('capi.worker', f'Processing query: {endpoint}') - # XXX - self.capi_response_queue.put( - CAPIFailedRequest(f'Unable to connect to endpoint {endpoint}') - ) - self.tk_master.event_generate('<<CAPIResponse>>') - continue - # XXX try: r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore r.raise_for_status() # Typically 403 "Forbidden" on token expiry From 018b4563b357a1fd6e4f37985e9609434fcf22e9 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 16:12:56 +0100 Subject: [PATCH 159/316] CAPI: return good data from worker & start handling reauth --- EDMarketConnector.py | 7 +++++++ companion.py | 14 ++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index ca30c484..7c13a739 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1075,6 +1075,13 @@ class AppWindow(object): # TODO: Set status text return + except companion.CredentialsError: + # Redirected back to Auth server - force full re-authentication + companion.session.dump(r) + companion.session.invalidate() + companion.session.retrying = False + companion.session.login() + # Companion API problem except companion.ServerLagging as e: err = str(e) diff --git a/companion.py b/companion.py index 7c67a892..4cca98da 100644 --- a/companion.py +++ b/companion.py @@ -642,12 +642,10 @@ class Session(object): if r.url.startswith(SERVER_AUTH): logger.info('Redirected back to Auth Server') - # Redirected back to Auth server - force full re-authentication - self.dump(r) - self.invalidate() - self.retrying = False - self.login() - raise CredentialsError() + self.capi_response_queue.put( + CAPIFailedRequest(f'Redirected back to Auth Server', exception=CredentialsError() + ) + continue elif 500 <= r.status_code < 600: # Server error. Typically 500 "Internal Server Error" if server is down @@ -656,6 +654,10 @@ class Session(object): # LANG: Frontier CAPI data retrieval failed with 5XX code raise ServerError(f'{_("Frontier CAPI server error")}: {r.status_code}') + self.capi_response_queue.put( + data + ) + logger.info('CAPI worker thread DONE') def capi_query_close_worker(self) -> None: From 454c9f3d0c2477a657e249fcb0fadcaea936f89c Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 16 Aug 2021 16:27:00 +0100 Subject: [PATCH 160/316] CAPI: Continuing code moving/cleanup up/testing 1. Sending back a spurious CredentialsError() to check it's now handled, but other issues blocking the code from getting there currently. 2. Move the "Update suit data from CAPI" call into the AppWindow handler. 3. Move some more of the old query() exception handling into the worker. 4. session.query() no longer returns anything, and neither should anything that calls it (like session.station()). This is going to take quite some unwinding and code moving. --- EDMarketConnector.py | 5 ++- companion.py | 85 ++++++++++++++++++++------------------------ 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 7c13a739..ec000e78 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -934,7 +934,7 @@ class AppWindow(object): self.w.update_idletasks() querytime = int(time()) - data = companion.session.station() + companion.session.station() config.set('querytime', querytime) @@ -1117,6 +1117,9 @@ class AppWindow(object): if play_sound and play_bad: hotkeymgr.play_bad() + # Update Odyssey Suit data + companion.suit_update(data) + self.update_suit_text() self.suit_show_if_set() self.cooldown() diff --git a/companion.py b/companion.py index 4cca98da..0647f538 100644 --- a/companion.py +++ b/companion.py @@ -621,6 +621,10 @@ class Session(object): try: r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore r.raise_for_status() # Typically 403 "Forbidden" on token expiry + self.capi_response_queue.put( + CAPIFailedRequest(f'Redirected back to Auth Server', exception=CredentialsError()) + ) + continue data = CAPIData(r.json(), endpoint) # May also fail here if token expired since response is empty except requests.ConnectionError as e: @@ -631,6 +635,28 @@ class Session(object): continue # raise ServerConnectionError(f'Unable to connect to endpoint {endpoint}') from e + except (requests.HTTPError, ValueError) as e: + logger.exception('Frontier CAPI Auth: GET ') + self.dump(r) + self.close() + + if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication + logger.error('Frontier CAPI Auth: query failed after refresh') + self.invalidate() + self.retrying = False + self.login() + raise CredentialsError('query failed after refresh') from e + + elif self.login(): # Maybe our token expired. Re-authorize in any case + logger.debug('Initial query failed, but login() just worked, trying again...') + self.retrying = True + return self.query(endpoint) + + else: + self.retrying = False + logger.error('Frontier CAPI Auth: HTTP error or invalid JSON') + raise CredentialsError('HTTP error or invalid JSON') from e + except Exception as e: logger.debug('Attempting GET', exc_info=e) # LANG: Frontier CAPI data retrieval failed @@ -643,7 +669,7 @@ class Session(object): if r.url.startswith(SERVER_AUTH): logger.info('Redirected back to Auth Server') self.capi_response_queue.put( - CAPIFailedRequest(f'Redirected back to Auth Server', exception=CredentialsError() + CAPIFailedRequest(f'Redirected back to Auth Server', exception=CredentialsError()) ) continue @@ -654,6 +680,14 @@ class Session(object): # LANG: Frontier CAPI data retrieval failed with 5XX code raise ServerError(f'{_("Frontier CAPI server error")}: {r.status_code}') + self.retrying = False + + if endpoint == URL_QUERY and 'commander' not in data: + logger.error('No commander in returned data') + + if 'timestamp' not in data: + data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) # type: ignore + self.capi_response_queue.put( data ) @@ -664,7 +698,7 @@ class Session(object): """Ask the CAPI query thread to finish.""" self.capi_query_queue.put(None) - def query(self, endpoint: str) -> CAPIData: # noqa: CCR001, C901 + def query(self, endpoint: str) -> None: """Perform a query against the specified CAPI endpoint.""" logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: @@ -680,48 +714,10 @@ class Session(object): raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') self.capi_query_queue.put(endpoint) - try: - ... - except (requests.HTTPError, ValueError) as e: - logger.exception('Frontier CAPI Auth: GET ') - self.dump(r) - self.close() - - if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication - logger.error('Frontier CAPI Auth: query failed after refresh') - self.invalidate() - self.retrying = False - self.login() - raise CredentialsError('query failed after refresh') from e - - elif self.login(): # Maybe our token expired. Re-authorize in any case - logger.debug('Initial query failed, but login() just worked, trying again...') - self.retrying = True - return self.query(endpoint) - - else: - self.retrying = False - logger.error('Frontier CAPI Auth: HTTP error or invalid JSON') - raise CredentialsError('HTTP error or invalid JSON') from e - - self.retrying = False - if 'timestamp' not in data: - data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) # type: ignore - - # Update Odyssey Suit data - if endpoint == URL_QUERY: - self.suit_update(data) - - return data - - def profile(self) -> CAPIData: + def profile(self): """Perform general CAPI /profile endpoint query.""" - data = self.query(URL_QUERY) - if 'commander' not in data: - logger.error('No commander in returned data') - - return data + self.query(URL_QUERY) def station(self) -> CAPIData: # noqa: CCR001 """ @@ -735,10 +731,7 @@ class Session(object): :return: Possibly augmented CAPI data. """ - data = self.query(URL_QUERY) - if 'commander' not in data: - logger.error('No commander in returned data') - return data + self.query(URL_QUERY) if not data['commander'].get('docked') and not monitor.state['OnFoot']: return data From 23e67dab82d01eef1be292711d6e206cc004389f Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:40:49 +0100 Subject: [PATCH 161/316] companion: Move global constants into appropriate classes --- companion.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/companion.py b/companion.py index 0647f538..33c62db6 100644 --- a/companion.py +++ b/companion.py @@ -51,15 +51,6 @@ holdoff = 60 # be nice timeout = 10 # requests timeout auth_timeout = 30 # timeout for initial auth -# Currently the "Elite Dangerous Market Connector (EDCD/Athanasius)" one in -# Athanasius' Frontier account -# Obtain from https://auth.frontierstore.net/client/signup -CLIENT_ID = os.getenv('CLIENT_ID') or 'fb88d428-9110-475f-a3d2-dc151c2b9c7a' -SERVER_AUTH = 'https://auth.frontierstore.net' -URL_AUTH = '/auth' -URL_TOKEN = '/token' -URL_DECODE = '/decode' - USER_AGENT = f'EDCD-{appname}-{appversion()}' SERVER_LIVE = 'https://companion.orerve.net' @@ -239,6 +230,11 @@ class CmdrError(Exception): class Auth(object): """Handles authentication with the Frontier CAPI service via oAuth2.""" + # Currently the "Elite Dangerous Market Connector (EDCD/Athanasius)" one in + # Athanasius' Frontier account + # Obtain from https://auth.frontierstore.net/client/signup + CLIENT_ID = os.getenv('CLIENT_ID') or 'fb88d428-9110-475f-a3d2-dc151c2b9c7a' + def __init__(self, cmdr: str) -> None: self.cmdr: str = cmdr self.session = requests.Session() @@ -274,7 +270,7 @@ class Auth(object): logger.debug('We have a refresh token for that idx') data = { 'grant_type': 'refresh_token', - 'client_id': CLIENT_ID, + 'client_id': self.CLIENT_ID, 'refresh_token': tokens[idx], } @@ -313,7 +309,7 @@ class Auth(object): f'{SERVER_AUTH}{URL_AUTH}?response_type=code' f'&audience=frontier,steam,epic' f'&scope=auth capi' - f'&client_id={CLIENT_ID}' + f'&client_id={self.CLIENT_ID}' f'&code_challenge={challenge}' f'&code_challenge_method=S256' f'&state={self.state}' @@ -351,7 +347,7 @@ class Auth(object): logger.debug('Got code, posting it back...') request_data = { 'grant_type': 'authorization_code', - 'client_id': CLIENT_ID, + 'client_id': self.CLIENT_ID, 'code_verifier': self.verifier, 'code': data['code'][0], 'redirect_uri': protocolhandler.redirect, @@ -477,6 +473,10 @@ class Session(object): """Methods for handling Frontier Auth and CAPI queries.""" STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) + SERVER_AUTH = 'https://auth.frontierstore.net' + URL_AUTH = '/auth' + URL_TOKEN = '/token' + URL_DECODE = '/decode' def __init__(self) -> None: self.state = Session.STATE_INIT @@ -521,8 +521,8 @@ class Session(object): :return: True if login succeeded, False if re-authorization initiated. """ - if not CLIENT_ID: - logger.error('CLIENT_ID is None') + if not self.CLIENT_ID: + logger.error('self.CLIENT_ID is None') raise CredentialsError('cannot login without a valid Client ID') # TODO: WTF is the intent behind this logic ? From 692b014cda5a2e549e770fe4adc58c5da059beeb Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:41:35 +0100 Subject: [PATCH 162/316] companion: Correct import order of tkinter --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index 33c62db6..00bf8415 100644 --- a/companion.py +++ b/companion.py @@ -15,8 +15,8 @@ import numbers import os import random import threading -import tkinter as tk import time +import tkinter as tk import urllib.parse import webbrowser from builtins import object, range, str From a8231065562a9421189ca66bac478e0dde833451 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:43:38 +0100 Subject: [PATCH 163/316] companion: Rename SERVER_AUTH to FRONTIER_AUTH_SERVER Also moves back to global scope, as it's used in both Auth and Session classes. --- companion.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/companion.py b/companion.py index 00bf8415..070c8cf3 100644 --- a/companion.py +++ b/companion.py @@ -51,6 +51,8 @@ holdoff = 60 # be nice timeout = 10 # requests timeout auth_timeout = 30 # timeout for initial auth +# Used by both class Auth and Session +FRONTIER_AUTH_SERVER = 'https://auth.frontierstore.net' USER_AGENT = f'EDCD-{appname}-{appversion()}' SERVER_LIVE = 'https://companion.orerve.net' @@ -276,7 +278,7 @@ class Auth(object): logger.debug('Attempting refresh with Frontier...') try: - r = self.session.post(SERVER_AUTH + URL_TOKEN, data=data, timeout=auth_timeout) + r = self.session.post(FRONTIER_AUTH_SERVER + URL_TOKEN, data=data, timeout=auth_timeout) if r.status_code == requests.codes.ok: data = r.json() tokens[idx] = data.get('refresh_token', '') @@ -306,7 +308,7 @@ class Auth(object): logger.info(f'Trying auth from scratch for Commander "{self.cmdr}"') challenge = self.base64_url_encode(hashlib.sha256(self.verifier).digest()) webbrowser.open( - f'{SERVER_AUTH}{URL_AUTH}?response_type=code' + f'{FRONTIER_AUTH_SERVER}{URL_AUTH}?response_type=code' f'&audience=frontier,steam,epic' f'&scope=auth capi' f'&client_id={self.CLIENT_ID}' @@ -360,12 +362,12 @@ class Auth(object): # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True - r = self.session.post(SERVER_AUTH + URL_TOKEN, data=request_data, timeout=auth_timeout) + r = self.session.post(FRONTIER_AUTH_SERVER + URL_TOKEN, data=request_data, timeout=auth_timeout) data_token = r.json() if r.status_code == requests.codes.ok: # Now we need to /decode the token to check the customer_id against FID r = self.session.get( - SERVER_AUTH + URL_DECODE, + FRONTIER_AUTH_SERVER + URL_DECODE, headers={ 'Authorization': f'Bearer {data_token.get("access_token", "")}', 'Content-Type': 'application/json', @@ -473,7 +475,6 @@ class Session(object): """Methods for handling Frontier Auth and CAPI queries.""" STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) - SERVER_AUTH = 'https://auth.frontierstore.net' URL_AUTH = '/auth' URL_TOKEN = '/token' URL_DECODE = '/decode' @@ -666,7 +667,7 @@ class Session(object): ) continue - if r.url.startswith(SERVER_AUTH): + if r.url.startswith(FRONTIER_AUTH_SERVER): logger.info('Redirected back to Auth Server') self.capi_response_queue.put( CAPIFailedRequest(f'Redirected back to Auth Server', exception=CredentialsError()) From f5bde2dcc636fced03904592ae7de82d108ce518 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:45:03 +0100 Subject: [PATCH 164/316] companion: Rename URL_AUTH to FRONTIER_AUTH_PATH, in class Auth --- companion.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 070c8cf3..455bb07d 100644 --- a/companion.py +++ b/companion.py @@ -237,6 +237,8 @@ class Auth(object): # Obtain from https://auth.frontierstore.net/client/signup CLIENT_ID = os.getenv('CLIENT_ID') or 'fb88d428-9110-475f-a3d2-dc151c2b9c7a' + FRONTIER_AUTH_PATH = '/auth' + def __init__(self, cmdr: str) -> None: self.cmdr: str = cmdr self.session = requests.Session() @@ -308,7 +310,7 @@ class Auth(object): logger.info(f'Trying auth from scratch for Commander "{self.cmdr}"') challenge = self.base64_url_encode(hashlib.sha256(self.verifier).digest()) webbrowser.open( - f'{FRONTIER_AUTH_SERVER}{URL_AUTH}?response_type=code' + f'{FRONTIER_AUTH_SERVER}{self.FRONTIER_AUTH_PATH}?response_type=code' f'&audience=frontier,steam,epic' f'&scope=auth capi' f'&client_id={self.CLIENT_ID}' @@ -475,7 +477,6 @@ class Session(object): """Methods for handling Frontier Auth and CAPI queries.""" STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) - URL_AUTH = '/auth' URL_TOKEN = '/token' URL_DECODE = '/decode' From 52a6eeb82a74cc5beb97d162e680f157151dfaa2 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:46:47 +0100 Subject: [PATCH 165/316] companion: URL_TOKEN -> Auth.FRONTIER_PATH_TOKEN Also, rename FRONTIER_AUTH_PATH to FRONTIER_PATH_AUTH. --- companion.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/companion.py b/companion.py index 455bb07d..0497ec2c 100644 --- a/companion.py +++ b/companion.py @@ -237,7 +237,8 @@ class Auth(object): # Obtain from https://auth.frontierstore.net/client/signup CLIENT_ID = os.getenv('CLIENT_ID') or 'fb88d428-9110-475f-a3d2-dc151c2b9c7a' - FRONTIER_AUTH_PATH = '/auth' + FRONTIER_PATH_AUTH = '/auth' + FRONTIER_PATH_TOKEN = '/token' def __init__(self, cmdr: str) -> None: self.cmdr: str = cmdr @@ -280,7 +281,7 @@ class Auth(object): logger.debug('Attempting refresh with Frontier...') try: - r = self.session.post(FRONTIER_AUTH_SERVER + URL_TOKEN, data=data, timeout=auth_timeout) + r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_PATH_TOKEN, data=data, timeout=auth_timeout) if r.status_code == requests.codes.ok: data = r.json() tokens[idx] = data.get('refresh_token', '') @@ -310,7 +311,7 @@ class Auth(object): logger.info(f'Trying auth from scratch for Commander "{self.cmdr}"') challenge = self.base64_url_encode(hashlib.sha256(self.verifier).digest()) webbrowser.open( - f'{FRONTIER_AUTH_SERVER}{self.FRONTIER_AUTH_PATH}?response_type=code' + f'{FRONTIER_AUTH_SERVER}{self.FRONTIER_PATH_AUTH}?response_type=code' f'&audience=frontier,steam,epic' f'&scope=auth capi' f'&client_id={self.CLIENT_ID}' @@ -364,7 +365,7 @@ class Auth(object): # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True - r = self.session.post(FRONTIER_AUTH_SERVER + URL_TOKEN, data=request_data, timeout=auth_timeout) + r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_PATH_TOKEN, data=request_data, timeout=auth_timeout) data_token = r.json() if r.status_code == requests.codes.ok: # Now we need to /decode the token to check the customer_id against FID @@ -477,7 +478,6 @@ class Session(object): """Methods for handling Frontier Auth and CAPI queries.""" STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) - URL_TOKEN = '/token' URL_DECODE = '/decode' def __init__(self) -> None: From 661ffb100b746f7a73ddbcae7f72fa562e900a60 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:48:09 +0100 Subject: [PATCH 166/316] companion: URL_DECODE -> Auth.FRONTIER_PATH_DECODE --- companion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 0497ec2c..b0b7a870 100644 --- a/companion.py +++ b/companion.py @@ -239,6 +239,7 @@ class Auth(object): FRONTIER_PATH_AUTH = '/auth' FRONTIER_PATH_TOKEN = '/token' + FRONTIER_PATH_DECODE = '/decode' def __init__(self, cmdr: str) -> None: self.cmdr: str = cmdr @@ -370,7 +371,7 @@ class Auth(object): if r.status_code == requests.codes.ok: # Now we need to /decode the token to check the customer_id against FID r = self.session.get( - FRONTIER_AUTH_SERVER + URL_DECODE, + FRONTIER_AUTH_SERVER + self.FRONTIER_PATH_DECODE, headers={ 'Authorization': f'Bearer {data_token.get("access_token", "")}', 'Content-Type': 'application/json', @@ -478,7 +479,6 @@ class Session(object): """Methods for handling Frontier Auth and CAPI queries.""" STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) - URL_DECODE = '/decode' def __init__(self) -> None: self.state = Session.STATE_INIT From 6f9f71d69965475fab113a9c5a474cb01637071e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:49:15 +0100 Subject: [PATCH 167/316] companion: s/FRONTIER_PATH/FRONTIER_AUTH_PATH/g This is so as to keep Frontier *auth* separate from Frontier *CAPI*. --- companion.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/companion.py b/companion.py index b0b7a870..afaa198b 100644 --- a/companion.py +++ b/companion.py @@ -237,9 +237,9 @@ class Auth(object): # Obtain from https://auth.frontierstore.net/client/signup CLIENT_ID = os.getenv('CLIENT_ID') or 'fb88d428-9110-475f-a3d2-dc151c2b9c7a' - FRONTIER_PATH_AUTH = '/auth' - FRONTIER_PATH_TOKEN = '/token' - FRONTIER_PATH_DECODE = '/decode' + FRONTIER_AUTH_PATH_AUTH = '/auth' + FRONTIER_AUTH_PATH_TOKEN = '/token' + FRONTIER_AUTH_PATH_DECODE = '/decode' def __init__(self, cmdr: str) -> None: self.cmdr: str = cmdr @@ -282,7 +282,7 @@ class Auth(object): logger.debug('Attempting refresh with Frontier...') try: - r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_PATH_TOKEN, data=data, timeout=auth_timeout) + r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, data=data, timeout=auth_timeout) if r.status_code == requests.codes.ok: data = r.json() tokens[idx] = data.get('refresh_token', '') @@ -312,7 +312,7 @@ class Auth(object): logger.info(f'Trying auth from scratch for Commander "{self.cmdr}"') challenge = self.base64_url_encode(hashlib.sha256(self.verifier).digest()) webbrowser.open( - f'{FRONTIER_AUTH_SERVER}{self.FRONTIER_PATH_AUTH}?response_type=code' + f'{FRONTIER_AUTH_SERVER}{self.FRONTIER_AUTH_PATH_AUTH}?response_type=code' f'&audience=frontier,steam,epic' f'&scope=auth capi' f'&client_id={self.CLIENT_ID}' @@ -366,12 +366,12 @@ class Auth(object): # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True - r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_PATH_TOKEN, data=request_data, timeout=auth_timeout) + r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, data=request_data, timeout=auth_timeout) data_token = r.json() if r.status_code == requests.codes.ok: # Now we need to /decode the token to check the customer_id against FID r = self.session.get( - FRONTIER_AUTH_SERVER + self.FRONTIER_PATH_DECODE, + FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_DECODE, headers={ 'Authorization': f'Bearer {data_token.get("access_token", "")}', 'Content-Type': 'application/json', From 58ea0abd5e2e862b17bdbabe2a982780cd5f4efb Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:52:04 +0100 Subject: [PATCH 168/316] companion: s/URL_MARKET/Session.FRONTIER_CAPI_PATH_MARKET/g --- companion.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/companion.py b/companion.py index afaa198b..9822b501 100644 --- a/companion.py +++ b/companion.py @@ -57,8 +57,6 @@ USER_AGENT = f'EDCD-{appname}-{appversion()}' SERVER_LIVE = 'https://companion.orerve.net' SERVER_BETA = 'https://pts-companion.orerve.net' -URL_QUERY = '/profile' -URL_MARKET = '/market' URL_SHIPYARD = '/shipyard' commodity_map: Dict = {} @@ -480,6 +478,9 @@ class Session(object): STATE_INIT, STATE_AUTH, STATE_OK = list(range(3)) + FRONTIER_CAPI_PATH_PROFILE = '/profile' + FRONTIER_CAPI_PATH_MARKET = '/market' + def __init__(self) -> None: self.state = Session.STATE_INIT self.server: Optional[str] = None @@ -684,7 +685,7 @@ class Session(object): self.retrying = False - if endpoint == URL_QUERY and 'commander' not in data: + if endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in data: logger.error('No commander in returned data') if 'timestamp' not in data: @@ -719,7 +720,7 @@ class Session(object): def profile(self): """Perform general CAPI /profile endpoint query.""" - self.query(URL_QUERY) + self.query(self.FRONTIER_CAPI_PATH_PROFILE) def station(self) -> CAPIData: # noqa: CCR001 """ @@ -733,7 +734,7 @@ class Session(object): :return: Possibly augmented CAPI data. """ - self.query(URL_QUERY) + self.query(self.FRONTIER_CAPI_PATH_PROFILE) if not data['commander'].get('docked') and not monitor.state['OnFoot']: return data @@ -771,7 +772,7 @@ class Session(object): last_starport_id = int(last_starport.get('id')) if services.get('commodities'): - marketdata = self.query(URL_MARKET) + marketdata = self.query(self.FRONTIER_CAPI_PATH_MARKET) if last_starport_id != int(marketdata['id']): logger.warning(f"{last_starport_id!r} != {int(marketdata['id'])!r}") raise ServerLagging() From 305ba157ec8502652d61fa4b45e13a3ccfcd71e4 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:54:03 +0100 Subject: [PATCH 169/316] companion: s/URL_SHIPYARD/Session.FRONTIER_CAPI_PATH_SHIPYARD/g --- companion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/companion.py b/companion.py index 9822b501..e016c03e 100644 --- a/companion.py +++ b/companion.py @@ -57,7 +57,6 @@ USER_AGENT = f'EDCD-{appname}-{appversion()}' SERVER_LIVE = 'https://companion.orerve.net' SERVER_BETA = 'https://pts-companion.orerve.net' -URL_SHIPYARD = '/shipyard' commodity_map: Dict = {} @@ -80,7 +79,7 @@ class CAPIData(UserDict): if source_endpoint is None: return - if source_endpoint == URL_SHIPYARD and self.data.get('lastStarport'): + if source_endpoint == self.FRONTIER_CAPI_PATH_SHIPYARD and self.data.get('lastStarport'): # All the other endpoints may or may not have a lastStarport, but definitely wont have valid data # for this check, which means it'll just make noise for no reason while we're working on other things self.check_modules_ships() @@ -480,6 +479,7 @@ class Session(object): FRONTIER_CAPI_PATH_PROFILE = '/profile' FRONTIER_CAPI_PATH_MARKET = '/market' + FRONTIER_CAPI_PATH_SHIPYARD = '/shipyard' def __init__(self) -> None: self.state = Session.STATE_INIT @@ -782,7 +782,7 @@ class Session(object): data['lastStarport'].update(marketdata) if services.get('outfitting') or services.get('shipyard'): - shipdata = self.query(URL_SHIPYARD) + shipdata = self.query(self.FRONTIER_CAPI_PATH_SHIPYARD) if last_starport_id != int(shipdata['id']): logger.warning(f"{last_starport_id!r} != {int(shipdata['id'])!r}") raise ServerLagging() From 3fe097a9551ff4abb28f15605ea34e5ed5c13e60 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 14:57:21 +0100 Subject: [PATCH 170/316] companion: Start moving CAPI 'station' query into thread worker --- companion.py | 123 +++++++++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/companion.py b/companion.py index e016c03e..afd212c7 100644 --- a/companion.py +++ b/companion.py @@ -480,6 +480,9 @@ class Session(object): FRONTIER_CAPI_PATH_PROFILE = '/profile' FRONTIER_CAPI_PATH_MARKET = '/market' FRONTIER_CAPI_PATH_SHIPYARD = '/shipyard' + # This is a dummy value, to signal to Session.capi_query_worker that we + # the 'station' triplet of queries. + _CAPI_PATH_STATION = '_edmc_station' def __init__(self) -> None: self.state = Session.STATE_INIT @@ -621,6 +624,65 @@ class Session(object): break logger.trace_if('capi.worker', f'Processing query: {endpoint}') + self.query(self.FRONTIER_CAPI_PATH_PROFILE) + + if not data['commander'].get('docked') and not monitor.state['OnFoot']: + return data + + # Sanity checks in case data isn't as we expect, and maybe 'docked' flag + # is also lagging. + if (last_starport := data.get('lastStarport')) is None: + logger.error("No lastStarport in data!") + return data + + if ((last_starport_name := last_starport.get('name')) is None + or last_starport_name == ''): + # This could well be valid if you've been out exploring for a long + # time. + logger.warning("No lastStarport name!") + return data + + # WORKAROUND: n/a | 06-08-2021: Issue 1198 and https://issues.frontierstore.net/issue-detail/40706 + # -- strip "+" chars off star port names returned by the CAPI + last_starport_name = last_starport["name"] = last_starport_name.rstrip(" +") + + services = last_starport.get('services', {}) + if not isinstance(services, dict): + # Odyssey Alpha Phase 3 4.0.0.20 has been observed having + # this be an empty list when you've jumped to another system + # and not yet docked. As opposed to no services key at all + # or an empty dict. + logger.error(f'services is "{type(services)}", not dict !') + if __debug__: + self.dump_capi_data(data) + + # Set an empty dict so as to not have to retest below. + services = {} + + last_starport_id = int(last_starport.get('id')) + + if services.get('commodities'): + marketdata = self.query(self.FRONTIER_CAPI_PATH_MARKET) + if last_starport_id != int(marketdata['id']): + logger.warning(f"{last_starport_id!r} != {int(marketdata['id'])!r}") + raise ServerLagging() + + else: + marketdata['name'] = last_starport_name + data['lastStarport'].update(marketdata) + + if services.get('outfitting') or services.get('shipyard'): + shipdata = self.query(self.FRONTIER_CAPI_PATH_SHIPYARD) + if last_starport_id != int(shipdata['id']): + logger.warning(f"{last_starport_id!r} != {int(shipdata['id'])!r}") + raise ServerLagging() + + else: + shipdata['name'] = last_starport_name + data['lastStarport'].update(shipdata) + # WORKAROUND END + + return data try: r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore r.raise_for_status() # Typically 403 "Forbidden" on token expiry @@ -734,65 +796,8 @@ class Session(object): :return: Possibly augmented CAPI data. """ - self.query(self.FRONTIER_CAPI_PATH_PROFILE) - - if not data['commander'].get('docked') and not monitor.state['OnFoot']: - return data - - # Sanity checks in case data isn't as we expect, and maybe 'docked' flag - # is also lagging. - if (last_starport := data.get('lastStarport')) is None: - logger.error("No lastStarport in data!") - return data - - if ((last_starport_name := last_starport.get('name')) is None - or last_starport_name == ''): - # This could well be valid if you've been out exploring for a long - # time. - logger.warning("No lastStarport name!") - return data - - # WORKAROUND: n/a | 06-08-2021: Issue 1198 and https://issues.frontierstore.net/issue-detail/40706 - # -- strip "+" chars off star port names returned by the CAPI - last_starport_name = last_starport["name"] = last_starport_name.rstrip(" +") - - services = last_starport.get('services', {}) - if not isinstance(services, dict): - # Odyssey Alpha Phase 3 4.0.0.20 has been observed having - # this be an empty list when you've jumped to another system - # and not yet docked. As opposed to no services key at all - # or an empty dict. - logger.error(f'services is "{type(services)}", not dict !') - if __debug__: - self.dump_capi_data(data) - - # Set an empty dict so as to not have to retest below. - services = {} - - last_starport_id = int(last_starport.get('id')) - - if services.get('commodities'): - marketdata = self.query(self.FRONTIER_CAPI_PATH_MARKET) - if last_starport_id != int(marketdata['id']): - logger.warning(f"{last_starport_id!r} != {int(marketdata['id'])!r}") - raise ServerLagging() - - else: - marketdata['name'] = last_starport_name - data['lastStarport'].update(marketdata) - - if services.get('outfitting') or services.get('shipyard'): - shipdata = self.query(self.FRONTIER_CAPI_PATH_SHIPYARD) - if last_starport_id != int(shipdata['id']): - logger.warning(f"{last_starport_id!r} != {int(shipdata['id'])!r}") - raise ServerLagging() - - else: - shipdata['name'] = last_starport_name - data['lastStarport'].update(shipdata) -# WORKAROUND END - - return data + # Ask the thread worker to perform all three queries + self.capi_query_queue.put(_CAPI_PATH_STATION) ###################################################################### ###################################################################### From c1deb9113ecc68e2274c56cfb7123442b334e03a Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 16:12:58 +0100 Subject: [PATCH 171/316] CAPI: 'station' query going via work queue * Defined inner functions in the worker function to handle the actual queries. This allows for *them* to simply return data or raise exceptions to be caught by the main worker function. *IT* then handles returning any error appropriately via the queue to AppWindow. * Due to the coupling between AppWindow and these queries there are several extra parameters passed into the queue worker and then back out. This is largely due to having to split AppWindow.getandsend() into two functions: capi_request_data() and capi_handle_response(). This might get changed to use a class to encapsulate those values, rather than the bare tuple currently being used. * No full flake8 & mypy pass done yet. * Some companion.py globals renamed so their use is more obvious. --- EDMarketConnector.py | 40 +++--- companion.py | 285 +++++++++++++++++++++++++++---------------- 2 files changed, 203 insertions(+), 122 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index ec000e78..fcf56211 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -18,7 +18,7 @@ from os.path import dirname, join from queue import Queue from sys import platform from time import localtime, strftime, time -from typing import TYPE_CHECKING, Optional, Tuple +from typing import TYPE_CHECKING, Optional, Tuple, Union # Have this as early as possible for people running EDMarketConnector.exe # from cmd.exe or a bat file or similar. Else they might not be in the correct @@ -385,7 +385,7 @@ class AppWindow(object): def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out - self.holdofftime = config.get_int('querytime', default=0) + companion.holdoff + self.capi_query_holdoff_time = config.get_int('querytime', default=0) + companion.capi_query_cooldown self.capi_response_queue: Queue = Queue() companion.session.set_capi_response_queue(self.capi_response_queue) @@ -894,7 +894,7 @@ class AppWindow(object): return True - def capi_request_data(self, event=None, retrying: bool = False): # noqa: C901, CCR001 + def capi_request_data(self, event=None, retrying: bool = False): """ Perform CAPI data retrieval and associated actions. @@ -903,23 +903,23 @@ class AppWindow(object): """ auto_update = not event play_sound = (auto_update or int(event.type) == self.EVENT_VIRTUAL) and not config.get_int('hotkey_mute') - play_bad = False - err: Optional[str] = None if ( not monitor.cmdr or not monitor.mode or monitor.state['Captain'] or not monitor.system or monitor.mode == 'CQC' ): + logger.trace_if('capi.worker', 'CQC detected, aborting query') return # In CQC or on crew - do nothing if companion.session.state == companion.Session.STATE_AUTH: + logger.trace_if('capi.worker', 'Auth in progress? Aborting query') # Attempt another Auth self.login() return if not retrying: - if time() < self.holdofftime: # Was invoked by key while in cooldown - if play_sound and (self.holdofftime - time()) < companion.holdoff * 0.75: + if time() < self.capi_query_holdoff_time: # Was invoked by key while in cooldown + if play_sound and (self.capi_query_holdoff_time - time()) < companion.capi_query_cooldown * 0.75: self.status['text'] = '' hotkeymgr.play_bad() # Don't play sound in first few seconds to prevent repeats @@ -934,14 +934,23 @@ class AppWindow(object): self.w.update_idletasks() querytime = int(time()) - companion.session.station() + logger.trace_if('capi.worker', 'Requesting full station data') + companion.session.station(querytime=querytime, play_sound=play_sound) config.set('querytime', querytime) def capi_handle_response(self, event=None): """Handle the resulting data from a CAPI query.""" + play_bad = False + err: Optional[str] = None + + data: Union[companion.CAPIData, companion.CAPIFailedRequest] + querytime: int + play_sound: bool + auto_update: bool try: - data = self.capi_response_queue.get(block=False) + logger.trace_if('capi.worker', 'Pulling answer off queue') + data, querytime, play_sound, auto_update = self.capi_response_queue.get(block=False) if isinstance(data, companion.CAPIFailedRequest): logger.trace_if('capi.worker', f'Failed Request: {data.message}') if data.exception: @@ -950,6 +959,7 @@ class AppWindow(object): else: raise ValueError(data.message) + logger.trace_if('capi.worker', 'Answer is not a Failure') # Validation if 'commander' not in data: # This can happen with EGS Auth if no commander created yet @@ -972,6 +982,7 @@ class AppWindow(object): elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: # Companion API Commander doesn't match Journal + logger.trace_if('capi.worker', 'Raising CmdrError()') raise companion.CmdrError() elif auto_update and not monitor.state['OnFoot'] and not data['commander'].get('docked'): @@ -1010,7 +1021,7 @@ class AppWindow(object): f"{last_station!r} != {monitor.station!r}") raise companion.ServerLagging() - self.holdofftime = querytime + companion.holdoff + self.capi_query_holdoff_time = querytime + companion.capi_query_cooldown elif not monitor.state['OnFoot'] and data['ship']['id'] != monitor.state['ShipID']: # CAPI ship must match @@ -1025,6 +1036,7 @@ class AppWindow(object): raise companion.ServerLagging() else: + # TODO: Change to depend on its own CL arg if __debug__: # Recording companion.session.dump_capi_data(data) @@ -1068,7 +1080,7 @@ class AppWindow(object): err = 'Error: Exporting Market data' play_bad = True - self.holdofftime = querytime + companion.holdoff + self.capi_query_holdoff_time = querytime + companion.capi_query_cooldown except queue.Empty: logger.error('There was no response in the queue!') @@ -1118,7 +1130,7 @@ class AppWindow(object): hotkeymgr.play_bad() # Update Odyssey Suit data - companion.suit_update(data) + companion.session.suit_update(data) self.update_suit_text() self.suit_show_if_set() @@ -1398,10 +1410,10 @@ class AppWindow(object): def cooldown(self) -> None: """Display and update the cooldown timer for 'Update' button.""" - if time() < self.holdofftime: + if time() < self.capi_query_holdoff_time: # Update button in main window self.button['text'] = self.theme_button['text'] \ - = _('cooldown {SS}s').format(SS=int(self.holdofftime - time())) # LANG: Cooldown on 'Update' button + = _('cooldown {SS}s').format(SS=int(self.capi_query_holdoff_time - time())) # LANG: Cooldown on 'Update' button self.w.after(1000, self.cooldown) else: diff --git a/companion.py b/companion.py index afd212c7..27cb8207 100644 --- a/companion.py +++ b/companion.py @@ -47,8 +47,8 @@ else: # Define custom type for the dicts that hold CAPI data # CAPIData = NewType('CAPIData', Dict) -holdoff = 60 # be nice -timeout = 10 # requests timeout +capi_query_cooldown = 60 # be nice +capi_default_timeout = 10 # requests timeout auth_timeout = 30 # timeout for initial auth # Used by both class Auth and Session @@ -79,7 +79,7 @@ class CAPIData(UserDict): if source_endpoint is None: return - if source_endpoint == self.FRONTIER_CAPI_PATH_SHIPYARD and self.data.get('lastStarport'): + if source_endpoint == Session.FRONTIER_CAPI_PATH_SHIPYARD and self.data.get('lastStarport'): # All the other endpoints may or may not have a lastStarport, but definitely wont have valid data # for this check, which means it'll just make noise for no reason while we're working on other things self.check_modules_ships() @@ -527,7 +527,7 @@ class Session(object): :return: True if login succeeded, False if re-authorization initiated. """ - if not self.CLIENT_ID: + if not Auth.CLIENT_ID: logger.error('self.CLIENT_ID is None') raise CredentialsError('cannot login without a valid Client ID') @@ -617,88 +617,23 @@ class Session(object): """Worker thread that performs actual CAPI queries.""" logger.info('CAPI worker thread starting') - while True: - endpoint: Optional[str] = self.capi_query_queue.get() - if not endpoint: - logger.info('Empty queue message, exiting...') - break + def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: + """ + Perform a *single* CAPI endpoint query within the thread worker. - logger.trace_if('capi.worker', f'Processing query: {endpoint}') - self.query(self.FRONTIER_CAPI_PATH_PROFILE) - - if not data['commander'].get('docked') and not monitor.state['OnFoot']: - return data - - # Sanity checks in case data isn't as we expect, and maybe 'docked' flag - # is also lagging. - if (last_starport := data.get('lastStarport')) is None: - logger.error("No lastStarport in data!") - return data - - if ((last_starport_name := last_starport.get('name')) is None - or last_starport_name == ''): - # This could well be valid if you've been out exploring for a long - # time. - logger.warning("No lastStarport name!") - return data - - # WORKAROUND: n/a | 06-08-2021: Issue 1198 and https://issues.frontierstore.net/issue-detail/40706 - # -- strip "+" chars off star port names returned by the CAPI - last_starport_name = last_starport["name"] = last_starport_name.rstrip(" +") - - services = last_starport.get('services', {}) - if not isinstance(services, dict): - # Odyssey Alpha Phase 3 4.0.0.20 has been observed having - # this be an empty list when you've jumped to another system - # and not yet docked. As opposed to no services key at all - # or an empty dict. - logger.error(f'services is "{type(services)}", not dict !') - if __debug__: - self.dump_capi_data(data) - - # Set an empty dict so as to not have to retest below. - services = {} - - last_starport_id = int(last_starport.get('id')) - - if services.get('commodities'): - marketdata = self.query(self.FRONTIER_CAPI_PATH_MARKET) - if last_starport_id != int(marketdata['id']): - logger.warning(f"{last_starport_id!r} != {int(marketdata['id'])!r}") - raise ServerLagging() - - else: - marketdata['name'] = last_starport_name - data['lastStarport'].update(marketdata) - - if services.get('outfitting') or services.get('shipyard'): - shipdata = self.query(self.FRONTIER_CAPI_PATH_SHIPYARD) - if last_starport_id != int(shipdata['id']): - logger.warning(f"{last_starport_id!r} != {int(shipdata['id'])!r}") - raise ServerLagging() - - else: - shipdata['name'] = last_starport_name - data['lastStarport'].update(shipdata) - # WORKAROUND END - - return data + :param capi_endpoint: An actual Frontier CAPI endpoint to query. + :param timeout: requests query timeout to use. + :return: The resulting CAPI data, of type CAPIData. + """ + capi_data: CAPIData try: - r = self.session.get(self.server + endpoint, timeout=timeout) # type: ignore + r = self.session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore r.raise_for_status() # Typically 403 "Forbidden" on token expiry - self.capi_response_queue.put( - CAPIFailedRequest(f'Redirected back to Auth Server', exception=CredentialsError()) - ) - continue - data = CAPIData(r.json(), endpoint) # May also fail here if token expired since response is empty + capi_data = CAPIData(r.json(), capi_endpoint) # May also fail here if token expired since response is empty except requests.ConnectionError as e: - logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {endpoint})') - self.capi_response_queue.put( - CAPIFailedRequest(f'Unable to connect to endpoint {endpoint}', exception=e) - ) - continue - # raise ServerConnectionError(f'Unable to connect to endpoint {endpoint}') from e + logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {capi_endpoint})') + raise ServerConnectionError(f'Unable to connect to endpoint: {capi_endpoint}') from e except (requests.HTTPError, ValueError) as e: logger.exception('Frontier CAPI Auth: GET ') @@ -712,10 +647,12 @@ class Session(object): self.login() raise CredentialsError('query failed after refresh') from e + # TODO: Better to return error and have upstream re-try auth ? elif self.login(): # Maybe our token expired. Re-authorize in any case logger.debug('Initial query failed, but login() just worked, trying again...') self.retrying = True - return self.query(endpoint) + # TODO: This, or raise (custom?) exception for upstream to do it? + return capi_single_query(capi_endpoint) else: self.retrying = False @@ -725,18 +662,11 @@ class Session(object): except Exception as e: logger.debug('Attempting GET', exc_info=e) # LANG: Frontier CAPI data retrieval failed - # raise ServerError(f'{_("Frontier CAPI query failure")}: {endpoint}') from e - self.capi_response_queue.put( - CAPIFailedRequest(f'Frontier CAPI query failure: {endpoint}', exception=e) - ) - continue + raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e if r.url.startswith(FRONTIER_AUTH_SERVER): logger.info('Redirected back to Auth Server') - self.capi_response_queue.put( - CAPIFailedRequest(f'Redirected back to Auth Server', exception=CredentialsError()) - ) - continue + raise CredentialsError('Redirected back to Auth Server') elif 500 <= r.status_code < 600: # Server error. Typically 500 "Internal Server Error" if server is down @@ -747,15 +677,145 @@ class Session(object): self.retrying = False - if endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in data: + if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data: logger.error('No commander in returned data') - if 'timestamp' not in data: - data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) # type: ignore + if 'timestamp' not in capi_data: + capi_data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) # type: ignore - self.capi_response_queue.put( - data - ) + return capi_data + + def capi_station_queries(timeout: int = capi_default_timeout) -> CAPIData: + """ + Perform all 'station' queries for the caller. + + A /profile query is performed to check that we are docked (or on foot) + and the station name and marketid match the prior Docked event. + If they do match, and the services list says they're present, also + retrieve CAPI market and/or shipyard/outfitting data and merge into + the /profile data. + + :param timeout: requests timeout to use. + :return: CAPIData instance with what we retrieved. + """ + station_data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE, timeout=timeout) + + if not station_data['commander'].get('docked') and not monitor.state['OnFoot']: + return station_data + + # Sanity checks in case data isn't as we expect, and maybe 'docked' flag + # is also lagging. + if (last_starport := station_data.get('lastStarport')) is None: + logger.error("No lastStarport in data!") + return station_data + + if ((last_starport_name := last_starport.get('name')) is None + or last_starport_name == ''): + # This could well be valid if you've been out exploring for a long + # time. + logger.warning("No lastStarport name!") + return station_data + + # WORKAROUND: n/a | 06-08-2021: Issue 1198 and https://issues.frontierstore.net/issue-detail/40706 + # -- strip "+" chars off star port names returned by the CAPI + last_starport_name = last_starport["name"] = last_starport_name.rstrip(" +") + + services = last_starport.get('services', {}) + if not isinstance(services, dict): + # Odyssey Alpha Phase 3 4.0.0.20 has been observed having + # this be an empty list when you've jumped to another system + # and not yet docked. As opposed to no services key at all + # or an empty dict. + logger.error(f'services is "{type(services)}", not dict !') + # TODO: Change this to be dependent on its own CL arg + if __debug__: + self.dump_capi_data(station_data) + + # Set an empty dict so as to not have to retest below. + services = {} + + last_starport_id = int(last_starport.get('id')) + + if services.get('commodities'): + market_data = capi_single_query(self.FRONTIER_CAPI_PATH_MARKET, timeout=timeout) + if last_starport_id != int(market_data['id']): + logger.warning(f"{last_starport_id!r} != {int(market_data['id'])!r}") + raise ServerLagging() + + else: + market_data['name'] = last_starport_name + station_data['lastStarport'].update(market_data) + + if services.get('outfitting') or services.get('shipyard'): + shipyard_data = capi_single_query(self.FRONTIER_CAPI_PATH_SHIPYARD, timeout=timeout) + if last_starport_id != int(shipyard_data['id']): + logger.warning(f"{last_starport_id!r} != {int(shipyard_data['id'])!r}") + raise ServerLagging() + + else: + shipyard_data['name'] = last_starport_name + station_data['lastStarport'].update(shipyard_data) + # WORKAROUND END + + return station_data + + while True: + endpoint: Optional[str] + querytime: int + play_sound: bool + auto_update: bool + endpoint, querytime, play_sound, auto_update = self.capi_query_queue.get() + if not endpoint: + logger.info('Empty queue message, exiting...') + break + + logger.trace_if('capi.worker', f'Processing query: {endpoint}') + data: CAPIData + if endpoint == self._CAPI_PATH_STATION: + try: + data = capi_station_queries() + + except Exception as e: + self.capi_response_queue.put( + ( + CAPIFailedRequest( + message=e.args, + exception=e + ), + querytime, + play_sound, + auto_update + ) + ) + + else: + self.capi_response_queue.put( + (data, querytime, play_sound, auto_update) + ) + + else: + try: + data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE) + + except Exception as e: + self.capi_response_queue.put( + ( + CAPIFailedRequest( + message=e.args, + exception=e + ), + querytime, + play_sound, + auto_update + ) + ) + + else: + self.capi_response_queue.put( + (data, querytime, play_sound, auto_update) + ) + + self.tk_master.event_generate('<<CAPIResponse>>') logger.info('CAPI worker thread DONE') @@ -763,8 +823,16 @@ class Session(object): """Ask the CAPI query thread to finish.""" self.capi_query_queue.put(None) - def query(self, endpoint: str) -> None: - """Perform a query against the specified CAPI endpoint.""" + def query(self, endpoint: str, querytime: int, play_sound: bool = False, auto_update: bool = False) -> None: + """ + Perform a query against the specified CAPI endpoint. + + :param querytime: When this query was initiated. + :param play_sound: Whether the app should play a sound on error. + :param endpoint: The CAPI endpoint to query, might be a pseudo-value. + :param auto_update: Whether this request was triggered automatically. + :return: + """ logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: if self.login(): @@ -778,26 +846,27 @@ class Session(object): if conf_module.capi_pretend_down: raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') - self.capi_query_queue.put(endpoint) + self.capi_query_queue.put( + (endpoint, querytime, play_sound, auto_update) + ) def profile(self): """Perform general CAPI /profile endpoint query.""" self.query(self.FRONTIER_CAPI_PATH_PROFILE) - def station(self) -> CAPIData: # noqa: CCR001 + def station(self, querytime: int, play_sound: bool = False, auto_update: bool = False) -> CAPIData: """ Perform CAPI quer(y|ies) for station data. - A /profile query is performed to check that we are docked (or on foot) - and the station name and marketid match the prior Docked event. - If they do match, and the services list says they're present, also - retrieve CAPI market and/or shipyard/outfitting data and merge into - the /profile data. - + :param querytime: When this query was initiated. + :param play_sound: Whether the app should play a sound on error. + :param auto_update: Whether this request was triggered automatically. :return: Possibly augmented CAPI data. """ # Ask the thread worker to perform all three queries - self.capi_query_queue.put(_CAPI_PATH_STATION) + self.capi_query_queue.put( + (self._CAPI_PATH_STATION, querytime, play_sound, auto_update) + ) ###################################################################### ###################################################################### From 62b1f0dd0d5c231e96af7100b7e0c5375d9b6de6 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 16:34:54 +0100 Subject: [PATCH 172/316] companion: flake8 pass --- companion.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/companion.py b/companion.py index 27cb8207..c65460a2 100644 --- a/companion.py +++ b/companion.py @@ -279,7 +279,11 @@ class Auth(object): logger.debug('Attempting refresh with Frontier...') try: - r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, data=data, timeout=auth_timeout) + r = self.session.post( + FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, + data=data, + timeout=auth_timeout + ) if r.status_code == requests.codes.ok: data = r.json() tokens[idx] = data.get('refresh_token', '') @@ -363,7 +367,11 @@ class Auth(object): # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True - r = self.session.post(FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, data=request_data, timeout=auth_timeout) + r = self.session.post( + FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, + data=request_data, + timeout=auth_timeout + ) data_token = r.json() if r.status_code == requests.codes.ok: # Now we need to /decode the token to check the customer_id against FID @@ -472,6 +480,7 @@ class CAPIFailedRequest(): self.message = message self.exception = exception + class Session(object): """Methods for handling Frontier Auth and CAPI queries.""" @@ -505,9 +514,11 @@ class Session(object): logger.info('Done') def set_capi_response_queue(self, capi_response_queue: Queue) -> None: + """Set a reference to the CAPI response data queue.""" self.capi_response_queue = capi_response_queue def set_tk_master(self, master: tk.Tk) -> None: + """Set a reference to main UI Tk root window.""" self.tk_master = master ###################################################################### @@ -613,7 +624,7 @@ class Session(object): ###################################################################### # CAPI queries ###################################################################### - def capi_query_worker(self): + def capi_query_worker(self): # noqa: C901, CCR001 """Worker thread that performs actual CAPI queries.""" logger.info('CAPI worker thread starting') @@ -629,7 +640,8 @@ class Session(object): try: r = self.session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore r.raise_for_status() # Typically 403 "Forbidden" on token expiry - capi_data = CAPIData(r.json(), capi_endpoint) # May also fail here if token expired since response is empty + # May also fail here if token expired since response is empty + capi_data = CAPIData(r.json(), capi_endpoint) except requests.ConnectionError as e: logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {capi_endpoint})') @@ -681,11 +693,13 @@ class Session(object): logger.error('No commander in returned data') if 'timestamp' not in capi_data: - capi_data['timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date'])) # type: ignore + capi_data['timestamp'] = time.strftime( + '%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date']) + ) # type: ignore return capi_data - def capi_station_queries(timeout: int = capi_default_timeout) -> CAPIData: + def capi_station_queries(timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 """ Perform all 'station' queries for the caller. From e5d200311e24db57da7f5b0b390b7aa4fbfabc7e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 16:41:39 +0100 Subject: [PATCH 173/316] companion: mypy pass * Still need to change stats.py to use a cached copy of CAPI data, rather than it querying that itself. That means actually storing that cached copy whilst in the capi worker. --- companion.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/companion.py b/companion.py index c65460a2..fe500abe 100644 --- a/companion.py +++ b/companion.py @@ -694,8 +694,8 @@ class Session(object): if 'timestamp' not in capi_data: capi_data['timestamp'] = time.strftime( - '%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date']) - ) # type: ignore + '%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date']) # type: ignore + ) return capi_data @@ -850,7 +850,8 @@ class Session(object): logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: if self.login(): - return self.query(endpoint) + self.query(endpoint, querytime, play_sound=play_sound, auto_update=auto_update) + return elif self.state == Session.STATE_AUTH: logger.error('cannot make a query when unauthorized') @@ -864,18 +865,23 @@ class Session(object): (endpoint, querytime, play_sound, auto_update) ) - def profile(self): - """Perform general CAPI /profile endpoint query.""" - self.query(self.FRONTIER_CAPI_PATH_PROFILE) + def profile(self, querytime: int = int(time.time()), play_sound: bool = False, auto_update: bool = False) -> None: + """ + Perform general CAPI /profile endpoint query. - def station(self, querytime: int, play_sound: bool = False, auto_update: bool = False) -> CAPIData: + :param querytime: When this query was initiated. + :param play_sound: Whether the app should play a sound on error. + :param auto_update: Whether this request was triggered automatically. + """ + self.query(self.FRONTIER_CAPI_PATH_PROFILE, querytime=querytime, play_sound=play_sound, auto_update=auto_update) + + def station(self, querytime: int, play_sound: bool = False, auto_update: bool = False) -> None: """ Perform CAPI quer(y|ies) for station data. :param querytime: When this query was initiated. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. - :return: Possibly augmented CAPI data. """ # Ask the thread worker to perform all three queries self.capi_query_queue.put( From 3f817c0fc0b4ccdb0dd05c5654bfb19f6edc7f58 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 16:45:07 +0100 Subject: [PATCH 174/316] companion: TODO: store /profile data for use elsewhere --- companion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/companion.py b/companion.py index fe500abe..a9d57753 100644 --- a/companion.py +++ b/companion.py @@ -697,6 +697,8 @@ class Session(object): '%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date']) # type: ignore ) + # TODO: Store a copy of this if it was /profile, e.g. for use by + # stats.py return capi_data def capi_station_queries(timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 From 8d7072fda45dc7d1f590cdb68d1a4aedc1d6922e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 17 Aug 2021 16:45:41 +0100 Subject: [PATCH 175/316] stats.py: TODO: Use cached /profile CAPI data when available --- stats.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stats.py b/stats.py index b64a3261..6a9c19bf 100644 --- a/stats.py +++ b/stats.py @@ -275,6 +275,7 @@ class StatsDialog(): if not monitor.cmdr: return + # TODO: This needs to use cached data # LANG: Fetching data from Frontier CAPI in order to display on File > Status self.status['text'] = _('Fetching data...') self.parent.update_idletasks() From 92e3ce9f4b492a54cba2e165e0e70a1975a9d834 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 19 Aug 2021 12:47:24 +0100 Subject: [PATCH 176/316] CAPI: Notes/dummy classes for using them to encapsulate requests/responses --- companion.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/companion.py b/companion.py index a9d57753..954830bf 100644 --- a/companion.py +++ b/companion.py @@ -473,6 +473,16 @@ class Auth(object): return base64.urlsafe_b64encode(text).decode().replace('=', '') +class CAPIRequest(): + """Encapsulates a request for CAPI data.""" + ... + + +class CAPIResponse(): + """Encapsulates a response from CAPI quer(y|ies).""" + ... + + class CAPIFailedRequest(): """CAPI failed query error class.""" @@ -656,6 +666,8 @@ class Session(object): logger.error('Frontier CAPI Auth: query failed after refresh') self.invalidate() self.retrying = False + # TODO: This must NOT happen here, we need to signal back to the requesting + # code that it needs to sort out auth. self.login() raise CredentialsError('query failed after refresh') from e From dc705b92e5c3899ab1504db3343704be70e9136d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 23 Aug 2021 15:29:49 +0100 Subject: [PATCH 177/316] companion: Convert to EDMCCAPIRequest class, not bare tuple --- companion.py | 56 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/companion.py b/companion.py index 954830bf..a1c9b401 100644 --- a/companion.py +++ b/companion.py @@ -473,13 +473,24 @@ class Auth(object): return base64.urlsafe_b64encode(text).decode().replace('=', '') -class CAPIRequest(): +class EDMCCAPIRequest(): """Encapsulates a request for CAPI data.""" - ... + + endpoint: str # The CAPI query to perform. + querytime: int # When this query is considered to have started (time_t). + play_sound: bool # Whether to play good/bad sounds for success/failure. + auto_update: bool # Whether this was automatically triggered. + + def __init__(self, endpoint: str, querytime: int, play_sound: bool = False, auto_update: bool = False): + self.endpoint = endpoint + self.querytime = querytime + self.play_sound = play_sound + self.auto_update = auto_update -class CAPIResponse(): +class EDMCCAPIResponse(): """Encapsulates a response from CAPI quer(y|ies).""" + ... @@ -792,14 +803,15 @@ class Session(object): querytime: int play_sound: bool auto_update: bool - endpoint, querytime, play_sound, auto_update = self.capi_query_queue.get() - if not endpoint: + # endpoint, querytime, play_sound, auto_update = self.capi_query_queue.get() + query = self.capi_query_queue.get() + if not query.endpoint: logger.info('Empty queue message, exiting...') break - logger.trace_if('capi.worker', f'Processing query: {endpoint}') + logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') data: CAPIData - if endpoint == self._CAPI_PATH_STATION: + if query.endpoint == self._CAPI_PATH_STATION: try: data = capi_station_queries() @@ -810,15 +822,15 @@ class Session(object): message=e.args, exception=e ), - querytime, - play_sound, - auto_update + query.querytime, + query.play_sound, + query.auto_update ) ) else: self.capi_response_queue.put( - (data, querytime, play_sound, auto_update) + (data, query.querytime, query.play_sound, query.auto_update) ) else: @@ -832,15 +844,15 @@ class Session(object): message=e.args, exception=e ), - querytime, - play_sound, - auto_update + query.querytime, + query.play_sound, + query.auto_update ) ) else: self.capi_response_queue.put( - (data, querytime, play_sound, auto_update) + (data, query.querytime, query.play_sound, query.auto_update) ) self.tk_master.event_generate('<<CAPIResponse>>') @@ -876,7 +888,12 @@ class Session(object): raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') self.capi_query_queue.put( - (endpoint, querytime, play_sound, auto_update) + EDMCCAPIRequest( + endpoint=endpoint, + querytime=querytime, + play_sound=play_sound, + auto_update=auto_update + ) ) def profile(self, querytime: int = int(time.time()), play_sound: bool = False, auto_update: bool = False) -> None: @@ -899,7 +916,12 @@ class Session(object): """ # Ask the thread worker to perform all three queries self.capi_query_queue.put( - (self._CAPI_PATH_STATION, querytime, play_sound, auto_update) + EDMCCAPIRequest( + endpoint=self._CAPI_PATH_STATION, + querytime=querytime, + play_sound=play_sound, + auto_update=auto_update + ) ) ###################################################################### From c6f93bd3c609bd4cb87e2966549b74c628a952a0 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 23 Aug 2021 15:36:21 +0100 Subject: [PATCH 178/316] companion: Update "close down" check for using EDMCAPIRequest --- companion.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/companion.py b/companion.py index a1c9b401..a689e957 100644 --- a/companion.py +++ b/companion.py @@ -803,11 +803,15 @@ class Session(object): querytime: int play_sound: bool auto_update: bool - # endpoint, querytime, play_sound, auto_update = self.capi_query_queue.get() query = self.capi_query_queue.get() - if not query.endpoint: - logger.info('Empty queue message, exiting...') - break + if not isinstance(query, EDMCCAPIRequest): + if query is not None: + logger.error("Item from queue wasn't an EDMCCAPIRequest") + break + + else: + logger.info('Empty queue message, exiting...') + break logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') data: CAPIData From f80623e025796a5950589dcd28504e99f93add2d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 23 Aug 2021 16:28:26 +0100 Subject: [PATCH 179/316] CAPI: Convert full Update flow to class passing * Base the following on common EDMCCAPIReturn: EDMCFailedrequest, EDMCCAPIRequest, EDMCCAPIResponse. This saves repeating a bunch of variable types and comments. * Use the above throughout the 'Update' button flow. * Still need to address 'Save Raw Data', i.e. AppWindow.save_raw(). --- EDMarketConnector.py | 133 ++++++++++++++++++++++++------------------- companion.py | 118 ++++++++++++++++++++++++++------------ 2 files changed, 157 insertions(+), 94 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index fcf56211..170a9a86 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -935,77 +935,86 @@ class AppWindow(object): querytime = int(time()) logger.trace_if('capi.worker', 'Requesting full station data') - companion.session.station(querytime=querytime, play_sound=play_sound) + companion.session.station( + querytime=querytime, retrying=retrying, play_sound=play_sound + ) config.set('querytime', querytime) - - def capi_handle_response(self, event=None): + def capi_handle_response(self, event=None): # noqa: C901, CCR001 """Handle the resulting data from a CAPI query.""" - play_bad = False + play_bad: bool = False err: Optional[str] = None - data: Union[companion.CAPIData, companion.CAPIFailedRequest] - querytime: int - play_sound: bool - auto_update: bool + capi_response: Union[companion.CAPIFailedRequest, companion.EDMCCAPIResponse] try: logger.trace_if('capi.worker', 'Pulling answer off queue') - data, querytime, play_sound, auto_update = self.capi_response_queue.get(block=False) - if isinstance(data, companion.CAPIFailedRequest): - logger.trace_if('capi.worker', f'Failed Request: {data.message}') - if data.exception: - raise data.exception + capi_response = self.capi_response_queue.get(block=False) + if isinstance(capi_response, companion.CAPIFailedRequest): + logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') + if capi_response.exception: + raise capi_response.exception else: - raise ValueError(data.message) + raise ValueError(capi_response.message) logger.trace_if('capi.worker', 'Answer is not a Failure') + if not isinstance(capi_response, companion.EDMCCAPIResponse): + msg = f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}" + logger.error(msg) + raise ValueError(msg) + # Validation - if 'commander' not in data: + if 'commander' not in capi_response.capi_data: # This can happen with EGS Auth if no commander created yet # LANG: No data was returned for the commander from the Frontier CAPI err = self.status['text'] = _('CAPI: No commander data returned') - elif not data.get('commander', {}).get('name'): + elif not capi_response.capi_data.get('commander', {}).get('name'): # LANG: We didn't have the commander name when we should have err = self.status['text'] = _("Who are you?!") # Shouldn't happen - elif (not data.get('lastSystem', {}).get('name') - or (data['commander'].get('docked') - and not data.get('lastStarport', {}).get('name'))): + elif (not capi_response.capi_data.get('lastSystem', {}).get('name') + or (capi_response.capi_data['commander'].get('docked') + and not capi_response.capi_data.get('lastStarport', {}).get('name'))): # LANG: We don't know where the commander is, when we should err = self.status['text'] = _("Where are you?!") # Shouldn't happen - elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): + elif ( + not capi_response.capi_data.get('ship', {}).get('name') + or not capi_response.capi_data.get('ship', {}).get('modules') + ): # LANG: We don't know what ship the commander is in, when we should err = self.status['text'] = _("What are you flying?!") # Shouldn't happen - elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: + elif monitor.cmdr and capi_response.capi_data['commander']['name'] != monitor.cmdr: # Companion API Commander doesn't match Journal logger.trace_if('capi.worker', 'Raising CmdrError()') raise companion.CmdrError() - elif auto_update and not monitor.state['OnFoot'] and not data['commander'].get('docked'): + elif ( + capi_response.auto_update and not monitor.state['OnFoot'] + and not capi_response.capi_data['commander'].get('docked') + ): # auto update is only when just docked - logger.warning(f"{auto_update!r} and not {monitor.state['OnFoot']!r} and " - f"not {data['commander'].get('docked')!r}") + logger.warning(f"{capi_response.auto_update!r} and not {monitor.state['OnFoot']!r} and " + f"not {capi_response.capi_data['commander'].get('docked')!r}") raise companion.ServerLagging() - elif data['lastSystem']['name'] != monitor.system: + elif capi_response.capi_data['lastSystem']['name'] != monitor.system: # CAPI system must match last journal one - logger.warning(f"{data['lastSystem']['name']!r} != {monitor.system!r}") + logger.warning(f"{capi_response.capi_data['lastSystem']['name']!r} != {monitor.system!r}") raise companion.ServerLagging() - elif data['lastStarport']['name'] != monitor.station: + elif capi_response.capi_data['lastStarport']['name'] != monitor.station: if monitor.state['OnFoot'] and monitor.station: - logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " + logger.warning(f"({capi_response.capi_data['lastStarport']['name']!r} != {monitor.station!r}) AND " f"{monitor.state['OnFoot']!r} and {monitor.station!r}") raise companion.ServerLagging() else: last_station = None - if data['commander']['docked']: - last_station = data['lastStarport']['name'] + if capi_response.capi_data['commander']['docked']: + last_station = capi_response.capi_data['lastStarport']['name'] if monitor.station is None: # Likely (re-)Embarked on ship docked at an EDO settlement. @@ -1017,33 +1026,40 @@ class AppWindow(object): if last_station != monitor.station: # CAPI lastStarport must match - logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " - f"{last_station!r} != {monitor.station!r}") + logger.warning(f"({capi_response.capi_data['lastStarport']['name']!r} != {monitor.station!r})" + f" AND {last_station!r} != {monitor.station!r}") raise companion.ServerLagging() - self.capi_query_holdoff_time = querytime + companion.capi_query_cooldown + self.capi_query_holdoff_time = capi_response.querytime + companion.capi_query_cooldown - elif not monitor.state['OnFoot'] and data['ship']['id'] != monitor.state['ShipID']: + elif not monitor.state['OnFoot'] and capi_response.capi_data['ship']['id'] != monitor.state['ShipID']: # CAPI ship must match logger.warning(f"not {monitor.state['OnFoot']!r} and " - f"{data['ship']['id']!r} != {monitor.state['ShipID']!r}") + f"{capi_response.capi_data['ship']['id']!r} != {monitor.state['ShipID']!r}") raise companion.ServerLagging() - elif not monitor.state['OnFoot'] and data['ship']['name'].lower() != monitor.state['ShipType']: + elif ( + not monitor.state['OnFoot'] + and capi_response.capi_data['ship']['name'].lower() != monitor.state['ShipType'] + ): # CAPI ship type must match logger.warning(f"not {monitor.state['OnFoot']!r} and " - f"{data['ship']['name'].lower()!r} != {monitor.state['ShipType']!r}") + f"{capi_response.capi_data['ship']['name'].lower()!r} != " + f"{monitor.state['ShipType']!r}") raise companion.ServerLagging() else: # TODO: Change to depend on its own CL arg if __debug__: # Recording - companion.session.dump_capi_data(data) + companion.session.dump_capi_data(capi_response.capi_data) if not monitor.state['ShipType']: # Started game in SRV or fighter - self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name']) - monitor.state['ShipID'] = data['ship']['id'] - monitor.state['ShipType'] = data['ship']['name'].lower() + self.ship['text'] = ship_name_map.get( + capi_response.capi_data['ship']['name'].lower(), + capi_response.capi_data['ship']['name'] + ) + monitor.state['ShipID'] = capi_response.capi_data['ship']['id'] + monitor.state['ShipType'] = capi_response.capi_data['ship']['name'].lower() if not monitor.state['Modules']: self.ship.configure(state=tk.DISABLED) @@ -1053,43 +1069,42 @@ class AppWindow(object): self.ship.configure(state=True) if monitor.state.get('SuitCurrent') is not None: - if (loadout := data.get('loadout')) is not None: + if (loadout := capi_response.capi_data.get('loadout')) is not None: if (suit := loadout.get('suit')) is not None: if (suitname := suit.get('edmcName')) is not None: # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there loadout_name = index_possibly_sparse_list( - data['loadouts'], loadout['loadoutSlotId'] + capi_response.capi_data['loadouts'], loadout['loadoutSlotId'] )['name'] self.suit['text'] = f'{suitname} ({loadout_name})' self.suit_show_if_set() - if data['commander'].get('credits') is not None: - monitor.state['Credits'] = data['commander']['credits'] - monitor.state['Loan'] = data['commander'].get('debt', 0) + if capi_response.capi_data['commander'].get('credits') is not None: + monitor.state['Credits'] = capi_response.capi_data['commander']['credits'] + monitor.state['Loan'] = capi_response.capi_data['commander'].get('debt', 0) # stuff we can do when not docked - err = plug.notify_newdata(data, monitor.is_beta) + err = plug.notify_newdata(capi_response.capi_data, monitor.is_beta) self.status['text'] = err and err or '' if err: play_bad = True # Export market data - if not self.export_market_data(data): + if not self.export_market_data(capi_response.capi_data): err = 'Error: Exporting Market data' play_bad = True - self.capi_query_holdoff_time = querytime + companion.capi_query_cooldown + self.capi_query_holdoff_time = capi_response.querytime + companion.capi_query_cooldown except queue.Empty: - logger.error('There was no response in the queue!') - # TODO: Set status text - return + logger.error('There was no response in the queue!') + # TODO: Set status text + return except companion.CredentialsError: # Redirected back to Auth server - force full re-authentication - companion.session.dump(r) companion.session.invalidate() companion.session.retrying = False companion.session.login() @@ -1097,7 +1112,7 @@ class AppWindow(object): # Companion API problem except companion.ServerLagging as e: err = str(e) - if retrying: + if capi_response.retrying: self.status['text'] = err play_bad = True @@ -1124,13 +1139,13 @@ class AppWindow(object): if not err: # not self.status['text']: # no errors # LANG: Time when we last obtained Frontier CAPI data - self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(querytime)) + self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(capi_response.querytime)) - if play_sound and play_bad: + if capi_response.play_sound and play_bad: hotkeymgr.play_bad() # Update Odyssey Suit data - companion.session.suit_update(data) + companion.session.suit_update(capi_response.capi_data) self.update_suit_text() self.suit_show_if_set() @@ -1413,7 +1428,9 @@ class AppWindow(object): if time() < self.capi_query_holdoff_time: # Update button in main window self.button['text'] = self.theme_button['text'] \ - = _('cooldown {SS}s').format(SS=int(self.capi_query_holdoff_time - time())) # LANG: Cooldown on 'Update' button + = _('cooldown {SS}s').format( # LANG: Cooldown on 'Update' button + SS=int(self.capi_query_holdoff_time - time()) + ) self.w.after(1000, self.cooldown) else: diff --git a/companion.py b/companion.py index a689e957..04f5bd74 100644 --- a/companion.py +++ b/companion.py @@ -473,33 +473,52 @@ class Auth(object): return base64.urlsafe_b64encode(text).decode().replace('=', '') -class EDMCCAPIRequest(): +class EDMCCAPIReturn: + """Base class for Request, Failure or Response.""" + + def __init__( + self, querytime: int, retrying: bool = False, + play_sound: bool = False, auto_update: bool = False + ): + self.querytime: int = querytime # When this query is considered to have started (time_t). + self.retrying: bool = retrying # Whether this is already a retry. + self.play_sound: bool = play_sound # Whether to play good/bad sounds for success/failure. + self.auto_update: bool = auto_update # Whether this was automatically triggered. + + +class EDMCCAPIRequest(EDMCCAPIReturn): """Encapsulates a request for CAPI data.""" - endpoint: str # The CAPI query to perform. - querytime: int # When this query is considered to have started (time_t). - play_sound: bool # Whether to play good/bad sounds for success/failure. - auto_update: bool # Whether this was automatically triggered. - - def __init__(self, endpoint: str, querytime: int, play_sound: bool = False, auto_update: bool = False): - self.endpoint = endpoint - self.querytime = querytime - self.play_sound = play_sound - self.auto_update = auto_update + def __init__( + self, endpoint: str, + querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + ): + super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + self.endpoint: str = endpoint # The CAPI query to perform. -class EDMCCAPIResponse(): +class EDMCCAPIResponse(EDMCCAPIReturn): """Encapsulates a response from CAPI quer(y|ies).""" - ... + def __init__( + self, capi_data: CAPIData, + querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + ): + super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + self.capi_data: CAPIData = capi_data # Frontier CAPI response, possibly augmented (station query) -class CAPIFailedRequest(): +class CAPIFailedRequest(EDMCCAPIReturn): """CAPI failed query error class.""" - def __init__(self, message, exception=None): - self.message = message - self.exception = exception + def __init__( + self, message: str, + querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False, + exception=None + ): + super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + self.message: str = message # User-friendly reason for failure. + self.exception: int = exception # Exception that recipient should raise. class Session(object): @@ -701,6 +720,7 @@ class Session(object): if r.url.startswith(FRONTIER_AUTH_SERVER): logger.info('Redirected back to Auth Server') + self.dump(r) raise CredentialsError('Redirected back to Auth Server') elif 500 <= r.status_code < 600: @@ -814,49 +834,59 @@ class Session(object): break logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') - data: CAPIData + capi_data: CAPIData if query.endpoint == self._CAPI_PATH_STATION: try: - data = capi_station_queries() + capi_data = capi_station_queries() except Exception as e: self.capi_response_queue.put( ( CAPIFailedRequest( message=e.args, - exception=e + exception=e, + querytime=query.querytime, + play_sound=query.play_sound, + auto_update=query.auto_update ), - query.querytime, - query.play_sound, - query.auto_update ) ) else: self.capi_response_queue.put( - (data, query.querytime, query.play_sound, query.auto_update) + EDMCCAPIResponse( + capi_data=capi_data, + querytime=query.querytime, + play_sound=query.play_sound, + auto_update=query.auto_update + ) ) else: try: - data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE) + capi_data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE) except Exception as e: self.capi_response_queue.put( ( CAPIFailedRequest( message=e.args, - exception=e + exception=e, + querytime=query.querytime, + play_sound=query.play_sound, + auto_update=query.auto_update ), - query.querytime, - query.play_sound, - query.auto_update ) ) else: self.capi_response_queue.put( - (data, query.querytime, query.play_sound, query.auto_update) + EDMCCAPIResponse( + capi_data=capi_data, + querytime=query.querytime, + play_sound=query.play_sound, + auto_update=query.auto_update + ) ) self.tk_master.event_generate('<<CAPIResponse>>') @@ -867,15 +897,17 @@ class Session(object): """Ask the CAPI query thread to finish.""" self.capi_query_queue.put(None) - def query(self, endpoint: str, querytime: int, play_sound: bool = False, auto_update: bool = False) -> None: + def query( + self, endpoint: str, + querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + ) -> None: """ Perform a query against the specified CAPI endpoint. :param querytime: When this query was initiated. + :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. - :param endpoint: The CAPI endpoint to query, might be a pseudo-value. :param auto_update: Whether this request was triggered automatically. - :return: """ logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: @@ -894,27 +926,40 @@ class Session(object): self.capi_query_queue.put( EDMCCAPIRequest( endpoint=endpoint, + retrying=retrying, querytime=querytime, play_sound=play_sound, auto_update=auto_update ) ) - def profile(self, querytime: int = int(time.time()), play_sound: bool = False, auto_update: bool = False) -> None: + def profile( + self, + querytime: int = int(time.time()), retrying: bool = False, + play_sound: bool = False, auto_update: bool = False + ) -> None: """ Perform general CAPI /profile endpoint query. :param querytime: When this query was initiated. + :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ - self.query(self.FRONTIER_CAPI_PATH_PROFILE, querytime=querytime, play_sound=play_sound, auto_update=auto_update) + self.query( + self.FRONTIER_CAPI_PATH_PROFILE, querytime=querytime, retrying=retrying, + play_sound=play_sound, auto_update=auto_update + ) - def station(self, querytime: int, play_sound: bool = False, auto_update: bool = False) -> None: + def station( + self, + querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + ) -> None: """ Perform CAPI quer(y|ies) for station data. :param querytime: When this query was initiated. + :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ @@ -923,6 +968,7 @@ class Session(object): EDMCCAPIRequest( endpoint=self._CAPI_PATH_STATION, querytime=querytime, + retrying=retrying, play_sound=play_sound, auto_update=auto_update ) From d53b567fca64cdd9d1cc64a3ad2e054ffc826177 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 23 Aug 2021 16:43:35 +0100 Subject: [PATCH 180/316] CAPI: s/querytime/query_time/ in variable names But not in the config setting, as that would require a migration. --- EDMarketConnector.py | 12 +++++------ companion.py | 48 ++++++++++++++++++++------------------------ 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 170a9a86..fc1b0432 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -933,12 +933,12 @@ class AppWindow(object): self.button['state'] = self.theme_button['state'] = tk.DISABLED self.w.update_idletasks() - querytime = int(time()) + query_time = int(time()) logger.trace_if('capi.worker', 'Requesting full station data') companion.session.station( - querytime=querytime, retrying=retrying, play_sound=play_sound + query_time=query_time, retrying=retrying, play_sound=play_sound ) - config.set('querytime', querytime) + config.set('querytime', query_time) def capi_handle_response(self, event=None): # noqa: C901, CCR001 """Handle the resulting data from a CAPI query.""" @@ -1030,7 +1030,7 @@ class AppWindow(object): f" AND {last_station!r} != {monitor.station!r}") raise companion.ServerLagging() - self.capi_query_holdoff_time = capi_response.querytime + companion.capi_query_cooldown + self.capi_query_holdoff_time = capi_response.query_time + companion.capi_query_cooldown elif not monitor.state['OnFoot'] and capi_response.capi_data['ship']['id'] != monitor.state['ShipID']: # CAPI ship must match @@ -1096,7 +1096,7 @@ class AppWindow(object): err = 'Error: Exporting Market data' play_bad = True - self.capi_query_holdoff_time = capi_response.querytime + companion.capi_query_cooldown + self.capi_query_holdoff_time = capi_response.query_time + companion.capi_query_cooldown except queue.Empty: logger.error('There was no response in the queue!') @@ -1139,7 +1139,7 @@ class AppWindow(object): if not err: # not self.status['text']: # no errors # LANG: Time when we last obtained Frontier CAPI data - self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(capi_response.querytime)) + self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(capi_response.query_time)) if capi_response.play_sound and play_bad: hotkeymgr.play_bad() diff --git a/companion.py b/companion.py index 04f5bd74..9f40cc5e 100644 --- a/companion.py +++ b/companion.py @@ -477,10 +477,10 @@ class EDMCCAPIReturn: """Base class for Request, Failure or Response.""" def __init__( - self, querytime: int, retrying: bool = False, + self, query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False ): - self.querytime: int = querytime # When this query is considered to have started (time_t). + self.query_time: int = query_time # When this query is considered to have started (time_t). self.retrying: bool = retrying # Whether this is already a retry. self.play_sound: bool = play_sound # Whether to play good/bad sounds for success/failure. self.auto_update: bool = auto_update # Whether this was automatically triggered. @@ -491,9 +491,9 @@ class EDMCCAPIRequest(EDMCCAPIReturn): def __init__( self, endpoint: str, - querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False ): - super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + super().__init__(query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update) self.endpoint: str = endpoint # The CAPI query to perform. @@ -502,9 +502,9 @@ class EDMCCAPIResponse(EDMCCAPIReturn): def __init__( self, capi_data: CAPIData, - querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False ): - super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + super().__init__(query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update) self.capi_data: CAPIData = capi_data # Frontier CAPI response, possibly augmented (station query) @@ -513,10 +513,10 @@ class CAPIFailedRequest(EDMCCAPIReturn): def __init__( self, message: str, - querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False, + query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False, exception=None ): - super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + super().__init__(query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update) self.message: str = message # User-friendly reason for failure. self.exception: int = exception # Exception that recipient should raise. @@ -819,10 +819,6 @@ class Session(object): return station_data while True: - endpoint: Optional[str] - querytime: int - play_sound: bool - auto_update: bool query = self.capi_query_queue.get() if not isinstance(query, EDMCCAPIRequest): if query is not None: @@ -845,7 +841,7 @@ class Session(object): CAPIFailedRequest( message=e.args, exception=e, - querytime=query.querytime, + query_time=query.query_time, play_sound=query.play_sound, auto_update=query.auto_update ), @@ -856,7 +852,7 @@ class Session(object): self.capi_response_queue.put( EDMCCAPIResponse( capi_data=capi_data, - querytime=query.querytime, + query_time=query.query_time, play_sound=query.play_sound, auto_update=query.auto_update ) @@ -872,7 +868,7 @@ class Session(object): CAPIFailedRequest( message=e.args, exception=e, - querytime=query.querytime, + query_time=query.query_time, play_sound=query.play_sound, auto_update=query.auto_update ), @@ -883,7 +879,7 @@ class Session(object): self.capi_response_queue.put( EDMCCAPIResponse( capi_data=capi_data, - querytime=query.querytime, + query_time=query.query_time, play_sound=query.play_sound, auto_update=query.auto_update ) @@ -899,12 +895,12 @@ class Session(object): def query( self, endpoint: str, - querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False ) -> None: """ Perform a query against the specified CAPI endpoint. - :param querytime: When this query was initiated. + :param query_time: When this query was initiated. :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. @@ -912,7 +908,7 @@ class Session(object): logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: if self.login(): - self.query(endpoint, querytime, play_sound=play_sound, auto_update=auto_update) + self.query(endpoint, query_time, play_sound=play_sound, auto_update=auto_update) return elif self.state == Session.STATE_AUTH: @@ -927,7 +923,7 @@ class Session(object): EDMCCAPIRequest( endpoint=endpoint, retrying=retrying, - querytime=querytime, + query_time=query_time, play_sound=play_sound, auto_update=auto_update ) @@ -935,30 +931,30 @@ class Session(object): def profile( self, - querytime: int = int(time.time()), retrying: bool = False, + query_time: int = int(time.time()), retrying: bool = False, play_sound: bool = False, auto_update: bool = False ) -> None: """ Perform general CAPI /profile endpoint query. - :param querytime: When this query was initiated. + :param query_time: When this query was initiated. :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ self.query( - self.FRONTIER_CAPI_PATH_PROFILE, querytime=querytime, retrying=retrying, + self.FRONTIER_CAPI_PATH_PROFILE, query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update ) def station( self, - querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False ) -> None: """ Perform CAPI quer(y|ies) for station data. - :param querytime: When this query was initiated. + :param query_time: When this query was initiated. :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. @@ -967,7 +963,7 @@ class Session(object): self.capi_query_queue.put( EDMCCAPIRequest( endpoint=self._CAPI_PATH_STATION, - querytime=querytime, + query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update From c71fe042cd96a777f955c47b5f815174ffafb530 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 23 Aug 2021 17:25:49 +0100 Subject: [PATCH 181/316] companion: Saw *raw* copies of CAPI data * Using global dict on class CAPIDataRaw as there should only ever be one of these. *Any* querying of CAPI should record the response data in this single place. * But as there'll be multiple endpoints recorded, class CAPIDataRawEndpoint uses instance variables. * It is the raw string from `requests.Response.content.decode(encoding='utf-8')` that is recorded, not the result of JSON decoding. * The recording happens in the worker capi_single_query() so it's as close as possible to where the data came back. The query_time is allowed to take on the default utc_now() value, rather than matching the nominal query_time of the request as passed in. --- companion.py | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/companion.py b/companion.py index 9f40cc5e..c3a7000f 100644 --- a/companion.py +++ b/companion.py @@ -9,6 +9,7 @@ protocol used for the callback. import base64 import collections import csv +import datetime import hashlib import json import numbers @@ -64,7 +65,10 @@ commodity_map: Dict = {} class CAPIData(UserDict): """CAPI Response.""" - def __init__(self, data: Union[str, Dict[str, Any], 'CAPIData', None] = None, source_endpoint: str = None) -> None: + def __init__( + self, + data: Union[str, Dict[str, Any], 'CAPIData', None] = None, source_endpoint: str = None + ) -> None: if data is None: super().__init__() elif isinstance(data, str): @@ -121,6 +125,38 @@ class CAPIData(UserDict): self.data['lastStarport']['ships'] = {'shipyard_list': {}, 'unavailable_list': []} +class CAPIDataRawEndpoint: + """Last received CAPI response for a specific endpoint.""" + + def __init__(self, raw_data: str, query_time: datetime.datetime): + self.query_time = query_time + self.raw_data = raw_data + # TODO: Maybe requests.response status ? + + +class CAPIDataRaw: + """The last obtained raw CAPI response for each end point.""" + + raw_data: Dict[str, CAPIDataRawEndpoint] = {} + + def record_endpoint( + self, endpoint: str, + raw_data: str, + query_time: datetime.datetime = datetime.datetime.utcnow() + ): + """Record the latest raw data for the given endpoint.""" + self.raw_data[endpoint] = CAPIDataRawEndpoint(raw_data, query_time) + + def __str__(self): + """Return a more readable string form of the data.""" + capi_data_str = '' + for e in self.raw_data.keys(): + capi_data_str += f'"{e}":\n{{\n\t"query_time": {self.raw_data[e].query_time},\n\t' \ + f'"raw_data": {self.raw_data[e].raw_data}\n}}\n\n' + + return capi_data_str + + def listify(thing: Union[List, Dict]) -> List: """ Convert actual JSON array or int-indexed dict into a Python list. @@ -542,6 +578,7 @@ class Session(object): self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query self.tk_master: Optional[tk.Tk] = None + self.capi_raw_data = CAPIDataRaw() logger.info('Starting CAPI queries thread...') self.capi_response_queue: Queue self.capi_query_queue: Queue = Queue() @@ -668,7 +705,7 @@ class Session(object): """Worker thread that performs actual CAPI queries.""" logger.info('CAPI worker thread starting') - def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: + def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 """ Perform a *single* CAPI endpoint query within the thread worker. @@ -681,7 +718,9 @@ class Session(object): r = self.session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore r.raise_for_status() # Typically 403 "Forbidden" on token expiry # May also fail here if token expired since response is empty - capi_data = CAPIData(r.json(), capi_endpoint) + capi_json = r.json() + capi_data = CAPIData(capi_json, capi_endpoint) + self.capi_raw_data.record_endpoint(capi_endpoint, r.content.decode(encoding='utf-8')) except requests.ConnectionError as e: logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {capi_endpoint})') From 1c93c3a9fa40d103b421d0f021bd19143de82711 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 23 Aug 2021 18:01:04 +0100 Subject: [PATCH 182/316] CAPI: 'Save Raw Data' now using cached copy It will write a file containing JSON that has a top level dict with a key per endpoint we have data for, and within each of those a dict with keys for query_time and the raw_data. Everything from the opening `{` in that raw_data value until the matching closing `}` is the raw data from the CAPI service. The whole file happily goes through `jq -S -C '.'` to examine the output. --- EDMarketConnector.py | 60 ++++++++++++++++---------------------------- companion.py | 9 ++++--- 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index fc1b0432..901eb763 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -4,7 +4,6 @@ import argparse import html -import json import locale import pathlib import queue @@ -1558,47 +1557,30 @@ class AppWindow(object): self.destroy() self.__class__.showing = False - def save_raw(self) -> None: # noqa: CCR001 # Not easily broken up. - """Save newly acquired CAPI data in the configured file.""" - # LANG: Status - Attempting to retrieve data from Frontier CAPI to save to file - self.status['text'] = _('Fetching data...') - self.w.update_idletasks() + def save_raw(self) -> None: + """ + Save any CAPI data already acquired to a file. - try: - data: CAPIData = companion.session.station() - self.status['text'] = '' - default_extension: str = '' + This specifically does *not* cause new queries to be performed, as the + purpose is to aid in diagnosing any issues that occurred during 'normal' + queries. + """ + default_extension: str = '' - if platform == 'darwin': - default_extension = '.json' + if platform == 'darwin': + default_extension = '.json' - last_system: str = data.get("lastSystem", {}).get("name", "Unknown") - last_starport: str = '' - - if data['commander'].get('docked'): - last_starport = '.' + data.get('lastStarport', {}).get('name', 'Unknown') - - timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime()) - f = tkinter.filedialog.asksaveasfilename( - parent=self.w, - defaultextension=default_extension, - filetypes=[('JSON', '.json'), ('All Files', '*')], - initialdir=config.get_str('outdir'), - initialfile=f'{last_system}{last_starport}.{timestamp}' - ) - if f: - with open(f, 'wb') as h: - h.write(json.dumps(dict(data), - ensure_ascii=False, - indent=2, - sort_keys=True, - separators=(',', ': ')).encode('utf-8')) - except companion.ServerError as e: - self.status['text'] = str(e) - - except Exception as e: - logger.debug('"other" exception', exc_info=e) - self.status['text'] = str(e) + timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime()) + f = tkinter.filedialog.asksaveasfilename( + parent=self.w, + defaultextension=default_extension, + filetypes=[('JSON', '.json'), ('All Files', '*')], + initialdir=config.get_str('outdir'), + initialfile=f'{monitor.system}{monitor.station}.{timestamp}' + ) + if f: + with open(f, 'wb') as h: + h.write(str(companion.session.capi_raw_data).encode(encoding='utf-8')) # def exit_tray(self, systray: 'SysTrayIcon') -> None: # """Tray icon is shutting down.""" diff --git a/companion.py b/companion.py index c3a7000f..36be1756 100644 --- a/companion.py +++ b/companion.py @@ -149,10 +149,13 @@ class CAPIDataRaw: def __str__(self): """Return a more readable string form of the data.""" - capi_data_str = '' + capi_data_str = '{' for e in self.raw_data.keys(): - capi_data_str += f'"{e}":\n{{\n\t"query_time": {self.raw_data[e].query_time},\n\t' \ - f'"raw_data": {self.raw_data[e].raw_data}\n}}\n\n' + capi_data_str += f'"{e}":\n{{\n\t"query_time": "{self.raw_data[e].query_time}",\n\t' \ + f'"raw_data": {self.raw_data[e].raw_data}\n}},\n\n' + + capi_data_str = capi_data_str.removesuffix(',\n\n') + capi_data_str += '\n\n}' return capi_data_str From 1dcefafc73c83c45c8f500b4940e883afd0cf7e3 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 24 Aug 2021 12:41:02 +0100 Subject: [PATCH 183/316] CAPI: Pass tk_response_event in Request to optionally use in response EDMC.py will also need to make CAPI queries using the new threaded method, but it has no tkinter, thus we need to pass in the event name when we expect one to be generated, and only generate it if this is not None. --- EDMarketConnector.py | 3 ++- companion.py | 48 +++++++++++++++++++++++++++++++------------- stats.py | 1 + 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 901eb763..0c9fba61 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -375,6 +375,7 @@ SHIPYARD_HTML_TEMPLATE = """ class AppWindow(object): """Define the main application window.""" + _CAPI_RESPONSE_TK_EVENT_NAME = '<<CAPIResponse>>' # Tkinter Event types EVENT_KEYPRESS = 2 EVENT_BUTTON = 4 @@ -655,7 +656,7 @@ class AppWindow(object): self.w.bind('<Return>', self.capi_request_data) self.w.bind('<KP_Enter>', self.capi_request_data) self.w.bind_all('<<Invoke>>', self.capi_request_data) # Ask for CAPI queries to be performed - self.w.bind_all('<<CAPIResponse>>', self.capi_handle_response) + self.w.bind_all(self._CAPI_RESPONSE_TK_EVENT_NAME, self.capi_handle_response) self.w.bind_all('<<JournalEvent>>', self.journal_event) # Journal monitoring self.w.bind_all('<<DashboardEvent>>', self.dashboard_event) # Dashboard monitoring self.w.bind_all('<<PluginError>>', self.plugin_error) # Statusbar diff --git a/companion.py b/companion.py index 36be1756..f45e3999 100644 --- a/companion.py +++ b/companion.py @@ -135,7 +135,7 @@ class CAPIDataRawEndpoint: class CAPIDataRaw: - """The last obtained raw CAPI response for each end point.""" + """The last obtained raw CAPI response for each endpoint.""" raw_data: Dict[str, CAPIDataRawEndpoint] = {} @@ -516,9 +516,10 @@ class EDMCCAPIReturn: """Base class for Request, Failure or Response.""" def __init__( - self, query_time: int, retrying: bool = False, - play_sound: bool = False, auto_update: bool = False + self, query_time: int, tk_response_event: Optional[str] = None, + retrying: bool = False, play_sound: bool = False, auto_update: bool = False ): + self.tk_response_event = tk_response_event # Name of tk event to generate when response queued. self.query_time: int = query_time # When this query is considered to have started (time_t). self.retrying: bool = retrying # Whether this is already a retry. self.play_sound: bool = play_sound # Whether to play good/bad sounds for success/failure. @@ -529,10 +530,14 @@ class EDMCCAPIRequest(EDMCCAPIReturn): """Encapsulates a request for CAPI data.""" def __init__( - self, endpoint: str, - query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + self, endpoint: str, query_time: int, + tk_response_event: Optional[str] = None, retrying: bool = False, + play_sound: bool = False, auto_update: bool = False ): - super().__init__(query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + super().__init__( + query_time=query_time, tk_response_event=tk_response_event, retrying=retrying, + play_sound=play_sound, auto_update=auto_update + ) self.endpoint: str = endpoint # The CAPI query to perform. @@ -927,7 +932,10 @@ class Session(object): ) ) - self.tk_master.event_generate('<<CAPIResponse>>') + # If the query came from EDMC.(py|exe) there's no tk to send an + # event too, so assume it will be polling there response queue. + if query.tk_response_event is not None: + self.tk_master.event_generate('<<CAPIResponse>>') logger.info('CAPI worker thread DONE') @@ -936,12 +944,15 @@ class Session(object): self.capi_query_queue.put(None) def query( - self, endpoint: str, - query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + self, endpoint: str, query_time: int, + tk_response_event: Optional[str] = None, retrying: bool = False, + play_sound: bool = False, auto_update: bool = False ) -> None: """ Perform a query against the specified CAPI endpoint. + :param endpoint: The CAPI endpoint to query. + :param tk_response_event: Name of tk event to generate when response queued. :param query_time: When this query was initiated. :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. @@ -950,7 +961,10 @@ class Session(object): logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') if self.state == Session.STATE_INIT: if self.login(): - self.query(endpoint, query_time, play_sound=play_sound, auto_update=auto_update) + self.query( + endpoint, query_time, tk_response_event=tk_response_event, play_sound=play_sound, + auto_update=auto_update + ) return elif self.state == Session.STATE_AUTH: @@ -964,6 +978,7 @@ class Session(object): self.capi_query_queue.put( EDMCCAPIRequest( endpoint=endpoint, + tk_response_event=tk_response_event, retrying=retrying, query_time=query_time, play_sound=play_sound, @@ -973,30 +988,34 @@ class Session(object): def profile( self, - query_time: int = int(time.time()), retrying: bool = False, + query_time: int = int(time.time()), + tk_response_event: Optional[str] = None, retrying: bool = False, play_sound: bool = False, auto_update: bool = False ) -> None: """ Perform general CAPI /profile endpoint query. :param query_time: When this query was initiated. + :param tk_response_event: Name of tk event to generate when response queued. :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ self.query( - self.FRONTIER_CAPI_PATH_PROFILE, query_time=query_time, retrying=retrying, + self.FRONTIER_CAPI_PATH_PROFILE, query_time=query_time, + tk_response_event=tk_response_event, retrying=retrying, play_sound=play_sound, auto_update=auto_update ) def station( - self, - query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + self, query_time: int, tk_response_event: Optional[str] = None, + retrying: bool = False, play_sound: bool = False, auto_update: bool = False ) -> None: """ Perform CAPI quer(y|ies) for station data. :param query_time: When this query was initiated. + :param tk_response_event: Name of tk event to generate when response queued. :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. @@ -1005,6 +1024,7 @@ class Session(object): self.capi_query_queue.put( EDMCCAPIRequest( endpoint=self._CAPI_PATH_STATION, + tk_response_event=tk_response_event, query_time=query_time, retrying=retrying, play_sound=play_sound, diff --git a/stats.py b/stats.py index 6a9c19bf..74f28e9c 100644 --- a/stats.py +++ b/stats.py @@ -281,6 +281,7 @@ class StatsDialog(): self.parent.update_idletasks() try: + # TODO: This should use cached data data = companion.session.profile() except companion.ServerError as e: From 3df688ecd4789c8f42caec65cd3fc5d558f13b8c Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 24 Aug 2021 12:43:33 +0100 Subject: [PATCH 184/316] CAPI: Pass in that tk_response_event from AppWindow.capi_request_data() --- EDMarketConnector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 0c9fba61..1ab27c33 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -936,7 +936,8 @@ class AppWindow(object): query_time = int(time()) logger.trace_if('capi.worker', 'Requesting full station data') companion.session.station( - query_time=query_time, retrying=retrying, play_sound=play_sound + query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, + retrying=retrying, play_sound=play_sound ) config.set('querytime', query_time) From 332d4cf97529b93b706b714a77310d6a270f66f1 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 24 Aug 2021 12:56:12 +0100 Subject: [PATCH 185/316] CAPI: EDMC - converted to the queue request/response format --- EDMC.py | 37 +++++++++++++++++++++++++++++++++++-- companion.py | 6 +++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/EDMC.py b/EDMC.py index 26dc6d7b..9ce0964d 100755 --- a/EDMC.py +++ b/EDMC.py @@ -6,9 +6,11 @@ import argparse import json import locale import os +import queue import re import sys from os.path import getmtime, join +from queue import Queue from time import sleep, time from typing import TYPE_CHECKING, Any, List, Optional @@ -270,9 +272,40 @@ sys.path: {sys.path}''' companion.session.login(monitor.cmdr, monitor.is_beta) + # Set up the response queue + capi_response_queue: Queue = Queue() + companion.session.set_capi_response_queue(capi_response_queue) + + # Initiate CAPI queries querytime = int(time()) - data = companion.session.station() - config.set('querytime', querytime) + companion.session.station(query_time=querytime) + + # Wait for the response + _capi_request_timeout = 60 + try: + capi_response = capi_response_queue.get(block=True, timeout=_capi_request_timeout) + + except queue.Empty: + logger.error(f'CAPI requests timed out after {_capi_request_timeout} seconds') + sys.exit(EXIT_SERVER) + + else: + if isinstance(capi_response, companion.CAPIFailedRequest): + logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') + if capi_response.exception: + raise capi_response.exception + + else: + raise ValueError(capi_response.message) + + logger.trace_if('capi.worker', 'Answer is not a Failure') + if not isinstance(capi_response, companion.EDMCCAPIResponse): + msg = f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}" + logger.error(msg) + raise ValueError(msg) + + data = capi_response.capi_data + config.set('querytime', querytime) # Validation if not deep_get(data, 'commander', 'name', default='').strip(): diff --git a/companion.py b/companion.py index f45e3999..f2b88136 100644 --- a/companion.py +++ b/companion.py @@ -587,7 +587,7 @@ class Session(object): self.tk_master: Optional[tk.Tk] = None self.capi_raw_data = CAPIDataRaw() - logger.info('Starting CAPI queries thread...') + logger.debug('Starting CAPI queries thread...') self.capi_response_queue: Queue self.capi_query_queue: Queue = Queue() self.capi_query_thread = threading.Thread( @@ -596,7 +596,7 @@ class Session(object): name='CAPI worker' ) self.capi_query_thread.start() - logger.info('Done') + logger.debug('Done') def set_capi_response_queue(self, capi_response_queue: Queue) -> None: """Set a reference to the CAPI response data queue.""" @@ -711,7 +711,7 @@ class Session(object): ###################################################################### def capi_query_worker(self): # noqa: C901, CCR001 """Worker thread that performs actual CAPI queries.""" - logger.info('CAPI worker thread starting') + logger.debug('CAPI worker thread starting') def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 """ From f12b8be74ce733cd3dde7ead601eb76013112d24 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 24 Aug 2021 13:04:25 +0100 Subject: [PATCH 186/316] EDMC: Fix `-d file` export of CAPI data --- EDMC.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMC.py b/EDMC.py index 9ce0964d..5568b19f 100755 --- a/EDMC.py +++ b/EDMC.py @@ -341,7 +341,7 @@ sys.path: {sys.path}''' # stuff we can do when not docked if args.d: logger.debug(f'Writing raw JSON data to "{args.d}"') - out = json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')) + out = json.dumps(dict(data), ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')) with open(args.d, 'wb') as f: f.write(out.encode("utf-8")) From 2e27a2ba00b4f2e5c818775d998942d56218d513 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 24 Aug 2021 13:22:13 +0100 Subject: [PATCH 187/316] CAPIData: Make JSON encodable, and use that elsewhere * It's no use `dict(CAPIData)` if that contains other `CAPIData`. So, rather than write something to do that recursively just implement a simple JSON Encoder class and specify its use. --- companion.py | 10 +++++++++- loadout.py | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index f2b88136..2d83a38c 100644 --- a/companion.py +++ b/companion.py @@ -125,6 +125,14 @@ class CAPIData(UserDict): self.data['lastStarport']['ships'] = {'shipyard_list': {}, 'unavailable_list': []} +class CAPIDataEncoder(json.JSONEncoder): + """Allow for json dumping via specified encoder.""" + + def default(self, o): + """Tell JSON encoder that we're actually just a dict.""" + return o.__dict__ + + class CAPIDataRawEndpoint: """Last received CAPI response for a specific endpoint.""" @@ -1099,7 +1107,7 @@ class Session(object): timestamp = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime()) with open(f'dump/{system}{station}.{timestamp}.json', 'wb') as h: - h.write(json.dumps(dict(data), + h.write(json.dumps(data, cls=CAPIDataEncoder, ensure_ascii=False, indent=2, sort_keys=True, diff --git a/loadout.py b/loadout.py index 92c9ff3d..dabce54b 100644 --- a/loadout.py +++ b/loadout.py @@ -13,7 +13,10 @@ import util_ships def export(data, filename=None): - string = json.dumps(companion.ship(data), ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ')) # pretty print + string = json.dumps( + companion.ship(data), cls=companion.CAPIDataEncoder, + ensure_ascii=False, indent=2, sort_keys=True, separators=(',', ': ') + ) # pretty print if filename: with open(filename, 'wt') as h: From c9d478222ac087bc16948d7f5dac05ce7eb99ad3 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 24 Aug 2021 13:52:48 +0100 Subject: [PATCH 188/316] EDMC: Provide EDDN Odyssey flag from monitor state This should have been set from the latest Journal file, and the key/value is in LoadGame in Odyssey. It's only documented as: "Whether the sending Cmdr has an Odyssey expansion." in the current EDDN Journal schema. Thus sending this as False if the Cmdr last logged into Horizons/base game should be OK. --- EDMC.py | 6 +++--- plugins/eddn.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/EDMC.py b/EDMC.py index 5568b19f..4e0428f0 100755 --- a/EDMC.py +++ b/EDMC.py @@ -441,9 +441,9 @@ sys.path: {sys.path}''' try: eddn_sender = eddn.EDDN(None) logger.debug('Sending Market, Outfitting and Shipyard data to EDDN...') - eddn_sender.export_commodities(data, monitor.is_beta) - eddn_sender.export_outfitting(data, monitor.is_beta) - eddn_sender.export_shipyard(data, monitor.is_beta) + eddn_sender.export_commodities(data, monitor.is_beta, monitor.state['Odyssey']) + eddn_sender.export_outfitting(data, monitor.is_beta, monitor.state['Odyssey']) + eddn_sender.export_shipyard(data, monitor.is_beta, monitor.state['Odyssey']) except Exception: logger.exception('Failed to send data to EDDN') diff --git a/plugins/eddn.py b/plugins/eddn.py index 3d0307fa..36a4cb94 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -310,6 +310,7 @@ Msg:\n{msg}''' :param data: a dict containing the starport data :param is_beta: whether or not we're currently in beta mode + :param is_odyssey: whether the account shows as having Odyssey expansion. """ commodities: List[OrderedDictT[str, Any]] = [] for commodity in data['lastStarport'].get('commodities') or []: @@ -790,7 +791,6 @@ def journal_entry( # noqa: C901, CCR001 :param state: `dict` - Current `monitor.state` data. :return: `str` - Error message, or `None` if no errors. """ - 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 From 4397acc6fb7367d6c1838a77b65cc55e5900c8b1 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 24 Aug 2021 14:06:12 +0100 Subject: [PATCH 189/316] CAPI: Status text when aborting due to CQC (& en.template updated) --- EDMarketConnector.py | 2 + L10n/en.template | 269 ++++++++++++++++++++++--------------------- 2 files changed, 138 insertions(+), 133 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 1ab27c33..ed92bbcf 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -909,6 +909,8 @@ class AppWindow(object): or not monitor.system or monitor.mode == 'CQC' ): logger.trace_if('capi.worker', 'CQC detected, aborting query') + # LANG: CAPI queries aborted because player is in CQC (Arena) + self.status['text'] = _('CQC detected, aborting CAPI query') return # In CQC or on crew - do nothing if companion.session.state == companion.Session.STATE_AUTH: diff --git a/L10n/en.template b/L10n/en.template index f62c5428..2221e523 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -1,172 +1,175 @@ /* Language name */ "!Language" = "English"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:212; */ "Error: Frontier CAPI didn't respond" = "Error: Frontier CAPI didn't respond"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:231; */ "Error: Frontier server is lagging" = "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:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:247; */ "Docked but unknown station: EDO Settlement?" = "Docked but unknown station: EDO Settlement?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:257; */ "Error: Invalid Credentials" = "Error: Invalid Credentials"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:273; */ "Error: Wrong Cmdr" = "Error: Wrong Cmdr"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:397; companion.py:482; */ "Error" = "Error"; -/* 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:380; companion.py:385; */ +/* 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:440; companion.py:445; */ "Error: Couldn't check token customer_id" = "Error: Couldn't check token customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:451; */ "Error: customer_id doesn't match!" = "Error: customer_id doesn't match!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:473; */ "Error: unable to get token" = "Error: unable to get token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:774; */ "Frontier CAPI query failure" = "Frontier CAPI query failure"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ +/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:786; */ "Frontier CAPI server error" = "Frontier CAPI server error"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:485; EDMarketConnector.py:780; EDMarketConnector.py:1440; */ "Update" = "Update"; -/* 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:507; prefs.py:843; */ +/* 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:568; prefs.py:866; */ "Always on top" = "Always on top"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:698; */ "Unknown" = "Unknown"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:767; */ "Error: Check E:D journal file location" = "Error: Check E:D journal file location"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:774; edsm.py:257; stats.py:50; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:776; EDMarketConnector.py:1199; */ "Role" = "Role"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:776; EDMarketConnector.py:1209; EDMarketConnector.py:1232; stats.py:365; */ "Ship" = "Ship"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:777; */ "Suit" = "Suit"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:778; prefs.py:606; stats.py:367; */ "System" = "System"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:779; prefs.py:624; prefs.py:761; stats.py:368; */ "Station" = "Station"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:782; EDMarketConnector.py:797; EDMarketConnector.py:800; EDMarketConnector.py:1917; */ "File" = "File"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:783; EDMarketConnector.py:798; EDMarketConnector.py:801; */ "Edit" = "Edit"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:784; */ "View" = "View"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:785; */ "Window" = "Window"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:786; EDMarketConnector.py:799; EDMarketConnector.py:802; */ "Help" = "Help"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:789; EDMarketConnector.py:815; EDMarketConnector.py:1486; */ "About {APP}" = "About {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:791; EDMarketConnector.py:814; */ "Check for Updates..." = "Check for Updates..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:792; EDMarketConnector.py:806; */ "Save Raw Data..." = "Save Raw Data..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:793; EDMarketConnector.py:805; stats.py:362; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:794; EDMarketConnector.py:812; */ "Privacy Policy" = "Privacy Policy"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:795; EDMarketConnector.py:813; EDMarketConnector.py:1520; */ "Release Notes" = "Release Notes"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:807; EDMarketConnector.py:1918; prefs.py:254; */ "Settings" = "Settings"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:808; */ "Exit" = "Exit"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:811; */ "Documentation" = "Documentation"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:818; ttkHyperlinkLabel.py:42; */ "Copy" = "Copy"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:824; */ "Logging in..." = "Logging in..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:840; EDMarketConnector.py:1351; */ "Authentication successful" = "Authentication successful"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:871; */ "You're not docked at a station!" = "You're not docked at a station!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:879; */ "Station doesn't have anything!" = "Station doesn't have anything!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:884; */ "Station doesn't have a market!" = "Station doesn't have a market!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:913; */ +"CQC detected, aborting CAPI query" = "CQC detected, aborting CAPI query"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:934; stats.py:280; */ "Fetching data..." = "Fetching data..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:973; */ "CAPI: No commander data returned" = "CAPI: No commander data returned"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:977; stats.py:299; */ "Who are you?!" = "Who are you?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:983; stats.py:309; */ "Where are you?!" = "Where are you?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:990; stats.py:314; */ "What are you flying?!" = "What are you flying?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1145; */ "Last updated at %H:%M:%S" = "Last updated at %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1174; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1175; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1176; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1434; */ "cooldown {SS}s" = "cooldown {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1546; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1611; */ "Shutting down..." = "Shutting down..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1906:1912; */ "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: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1916; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1927; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins Without Python 3.x Support"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -181,7 +184,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignore"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Default"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -214,193 +217,205 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDB Journal processing disabled. See Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:225; eddn.py:630; eddn.py:982; */ "Sending data to EDDN..." = "Sending data to EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:274; eddn.py:920; eddn.py:955; eddn.py:994; */ "Error: Can't connect to EDDN" = "Error: Can't connect to EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:292; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Error: EDMC is too old for EDDN. Please update."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:298; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Error: Validation Failed (EDMC Too Old?). See Log"; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:303; */ "EDDN Error: Returned {STATUS} status code" = "EDDN Error: Returned {STATUS} status code"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:710; */ "Send station data to the Elite Dangerous Data Network" = "Send station data to the Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:721; */ "Send system and scan data to the Elite Dangerous Data Network" = "Send system and scan data to the Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:732; */ "Delay sending until docked" = "Delay sending until docked"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:796; */ "EDDN journal handler disabled. See Log." = "EDDN journal handler disabled. See Log."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Send flight log and Cmdr status to EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map credentials"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Commander Name"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Key"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ "None" = "None"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "EDSM Handler disabled. See Log."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Error: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Error: Can't connect to EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Send flight log and Cmdr status to Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara credentials"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara disabled. See Log."; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Error: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Preferences"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Please choose what data to save"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Market data in CSV format file"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Market data in Trade Dangerous format file"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Ship loadout"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automatically update on docking"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "File location"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Change..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Browse..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Output"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D journal file location"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Keyboard shortcut"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "{APP} needs permission to use shortcuts"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Open System Preferences"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Only when Elite: Dangerous is the active app"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Play sound"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Disable Automatic Application Updates Check when in-game"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Preferred websites"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Shipyard"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Use alternate URL method"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Log Level"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Configuration"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Main UI privacy options"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Hide private group name in UI"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Hide multi-crew captain name"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privacy"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normal text"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Highlighted text"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Language"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Theme"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Dark"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparent"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "UI Scale Percentage"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 means Default{CR}Restart Required for{CR}changes to take effect!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Main window transparency"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 means fully opaque.{CR}Window is updated in real time"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Appearance"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Plugins folder"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Open"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Enabled Plugins"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Plugins Without Python 3.x Support:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Information on migrating plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Disabled Plugins"; /* stats.py: Cmdr stats; In files: stats.py:51; */ @@ -625,20 +640,8 @@ /* stats.py: Power rank; In files: stats.py:160; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:369; */ "Value" = "Value"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:378; */ "Ships" = "Ships"; - -/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ -"Main UI privacy options" = "Main UI privacy options"; - -/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ -"Hide private group name in UI" = "Hide private group name in UI"; - -/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ -"Hide multi-crew captain name" = "Hide multi-crew captain name"; - -/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ -"Privacy" = "Privacy"; From 030d79c9da8c9014397c25a8809fc4960987708f Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:31:08 +0100 Subject: [PATCH 190/316] EDMC: De-indent CAPI response checks --- EDMC.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/EDMC.py b/EDMC.py index 4e0428f0..842e0a33 100755 --- a/EDMC.py +++ b/EDMC.py @@ -289,23 +289,23 @@ sys.path: {sys.path}''' logger.error(f'CAPI requests timed out after {_capi_request_timeout} seconds') sys.exit(EXIT_SERVER) - else: - if isinstance(capi_response, companion.CAPIFailedRequest): - logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') - if capi_response.exception: - raise capi_response.exception + # noinspection DuplicatedCode + if isinstance(capi_response, companion.CAPIFailedRequest): + logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') + if capi_response.exception: + raise capi_response.exception - else: - raise ValueError(capi_response.message) + else: + raise ValueError(capi_response.message) - logger.trace_if('capi.worker', 'Answer is not a Failure') - if not isinstance(capi_response, companion.EDMCCAPIResponse): - msg = f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}" - logger.error(msg) - raise ValueError(msg) + logger.trace_if('capi.worker', 'Answer is not a Failure') + if not isinstance(capi_response, companion.EDMCCAPIResponse): + msg = f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}" + logger.error(msg) + raise ValueError(msg) - data = capi_response.capi_data - config.set('querytime', querytime) + data = capi_response.capi_data + config.set('querytime', querytime) # Validation if not deep_get(data, 'commander', 'name', default='').strip(): From 5710f3fd610c74aa2b608e4f50819b5031c6fbbc Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:33:52 +0100 Subject: [PATCH 191/316] CAPI: Only `import queue` in EDMarketConnector.py --- EDMarketConnector.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index ed92bbcf..5afc16b3 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -14,7 +14,6 @@ import webbrowser from builtins import object, str from os import chdir, environ from os.path import dirname, join -from queue import Queue from sys import platform from time import localtime, strftime, time from typing import TYPE_CHECKING, Optional, Tuple, Union @@ -386,7 +385,7 @@ class AppWindow(object): def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out self.capi_query_holdoff_time = config.get_int('querytime', default=0) + companion.capi_query_cooldown - self.capi_response_queue: Queue = Queue() + self.capi_response_queue: queue.Queue = queue.Queue() companion.session.set_capi_response_queue(self.capi_response_queue) self.w = master From dad2f95f64671c188f84d16bb8cde6e064062bb0 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:34:51 +0100 Subject: [PATCH 192/316] EDMarketConnector: Correct arg to tk overrideredirect to bool --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 5afc16b3..2845844d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1670,7 +1670,7 @@ class AppWindow(object): def oniconify(self, event=None) -> None: """Handle minimization of the application.""" - self.w.overrideredirect(0) # Can't iconize while overrideredirect + self.w.overrideredirect(False) # Can't iconize while overrideredirect self.w.iconify() self.w.update_idletasks() # Size and windows styles get recalculated here self.w.wait_visibility() # Need main window to be re-created before returning From 0456996cdd74fdb2e0ccf288a5eb98c6f947f44f Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:36:39 +0100 Subject: [PATCH 193/316] EDMarketConnector: Save querytime to config *before* triggering API --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 2845844d..940b4a40 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -936,11 +936,11 @@ class AppWindow(object): query_time = int(time()) logger.trace_if('capi.worker', 'Requesting full station data') + config.set('querytime', query_time) companion.session.station( query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, retrying=retrying, play_sound=play_sound ) - config.set('querytime', query_time) def capi_handle_response(self, event=None): # noqa: C901, CCR001 """Handle the resulting data from a CAPI query.""" From 69d0d03b9b81bfe0cd8052ef466a60f326ec5e3d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:42:53 +0100 Subject: [PATCH 194/316] CAPI: Rename to EDMCCAPIFailedRequest & comment queues * The 'EDMC' prefix on all these classes is to make it clear they're for internal passing around of requests/responses, rather than holding the literal raw CAPI request and response. * The request and response queues are now commented, including the detail that the response queue is created by the caller and then set 'here' by Session.set_capi_response_queue(). --- EDMC.py | 2 +- EDMarketConnector.py | 4 ++-- companion.py | 19 +++++++++++++------ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/EDMC.py b/EDMC.py index 842e0a33..cfd019e5 100755 --- a/EDMC.py +++ b/EDMC.py @@ -290,7 +290,7 @@ sys.path: {sys.path}''' sys.exit(EXIT_SERVER) # noinspection DuplicatedCode - if isinstance(capi_response, companion.CAPIFailedRequest): + if isinstance(capi_response, companion.EDMCCAPIFailedRequest): logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') if capi_response.exception: raise capi_response.exception diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 940b4a40..6ec7b32c 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -947,11 +947,11 @@ class AppWindow(object): play_bad: bool = False err: Optional[str] = None - capi_response: Union[companion.CAPIFailedRequest, companion.EDMCCAPIResponse] + capi_response: Union[companion.EDMCCAPIFailedRequest, companion.EDMCCAPIResponse] try: logger.trace_if('capi.worker', 'Pulling answer off queue') capi_response = self.capi_response_queue.get(block=False) - if isinstance(capi_response, companion.CAPIFailedRequest): + if isinstance(capi_response, companion.EDMCCAPIFailedRequest): logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') if capi_response.exception: raise capi_response.exception diff --git a/companion.py b/companion.py index 2d83a38c..85a4e4f6 100644 --- a/companion.py +++ b/companion.py @@ -560,7 +560,7 @@ class EDMCCAPIResponse(EDMCCAPIReturn): self.capi_data: CAPIData = capi_data # Frontier CAPI response, possibly augmented (station query) -class CAPIFailedRequest(EDMCCAPIReturn): +class EDMCCAPIFailedRequest(EDMCCAPIReturn): """CAPI failed query error class.""" def __init__( @@ -594,10 +594,17 @@ class Session(object): self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query self.tk_master: Optional[tk.Tk] = None - self.capi_raw_data = CAPIDataRaw() - logger.debug('Starting CAPI queries thread...') - self.capi_response_queue: Queue + self.capi_raw_data = CAPIDataRaw() # Cache of raw replies from CAPI service + # Queue that holds requests for CAPI queries, the items should always + # be EDMCCAPIRequest objects. self.capi_query_queue: Queue = Queue() + # This queue is used to pass the result, possibly a failure, of CAPI + # queries back to the requesting code (technically anything checking + # this queue, but it should be either EDMarketConnector.AppWindow or + # EDMC.py). Items may be EDMCCAPIResponse or EDMCCAPIFailedRequest. + # NB: This is created by the caller and set using self.set_capi_response_queue(). + self.capi_response_queue: Queue + logger.debug('Starting CAPI queries thread...') self.capi_query_thread = threading.Thread( target=self.capi_query_worker, daemon=True, @@ -893,7 +900,7 @@ class Session(object): except Exception as e: self.capi_response_queue.put( ( - CAPIFailedRequest( + EDMCCAPIFailedRequest( message=e.args, exception=e, query_time=query.query_time, @@ -920,7 +927,7 @@ class Session(object): except Exception as e: self.capi_response_queue.put( ( - CAPIFailedRequest( + EDMCCAPIFailedRequest( message=e.args, exception=e, query_time=query.query_time, From e1c6a0e9f87b4bf9fc5a68ba221d9fd971c973e9 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:50:56 +0100 Subject: [PATCH 195/316] CAPI: Create response queue in companion.Session No need to create in callers and then use setter. --- EDMC.py | 9 +++------ EDMarketConnector.py | 4 +--- companion.py | 7 +------ 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/EDMC.py b/EDMC.py index cfd019e5..ab65d726 100755 --- a/EDMC.py +++ b/EDMC.py @@ -10,7 +10,6 @@ import queue import re import sys from os.path import getmtime, join -from queue import Queue from time import sleep, time from typing import TYPE_CHECKING, Any, List, Optional @@ -272,10 +271,6 @@ sys.path: {sys.path}''' companion.session.login(monitor.cmdr, monitor.is_beta) - # Set up the response queue - capi_response_queue: Queue = Queue() - companion.session.set_capi_response_queue(capi_response_queue) - # Initiate CAPI queries querytime = int(time()) companion.session.station(query_time=querytime) @@ -283,7 +278,9 @@ sys.path: {sys.path}''' # Wait for the response _capi_request_timeout = 60 try: - capi_response = capi_response_queue.get(block=True, timeout=_capi_request_timeout) + capi_response = companion.session.capi_response_queue.get( + block=True, timeout=_capi_request_timeout + ) except queue.Empty: logger.error(f'CAPI requests timed out after {_capi_request_timeout} seconds') diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 6ec7b32c..b82bfe98 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -385,8 +385,6 @@ class AppWindow(object): def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out self.capi_query_holdoff_time = config.get_int('querytime', default=0) + companion.capi_query_cooldown - self.capi_response_queue: queue.Queue = queue.Queue() - companion.session.set_capi_response_queue(self.capi_response_queue) self.w = master self.w.title(applongname) @@ -950,7 +948,7 @@ class AppWindow(object): capi_response: Union[companion.EDMCCAPIFailedRequest, companion.EDMCCAPIResponse] try: logger.trace_if('capi.worker', 'Pulling answer off queue') - capi_response = self.capi_response_queue.get(block=False) + capi_response = companion.session.capi_response_queue.get(block=False) if isinstance(capi_response, companion.EDMCCAPIFailedRequest): logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') if capi_response.exception: diff --git a/companion.py b/companion.py index 85a4e4f6..4315a57c 100644 --- a/companion.py +++ b/companion.py @@ -602,8 +602,7 @@ class Session(object): # queries back to the requesting code (technically anything checking # this queue, but it should be either EDMarketConnector.AppWindow or # EDMC.py). Items may be EDMCCAPIResponse or EDMCCAPIFailedRequest. - # NB: This is created by the caller and set using self.set_capi_response_queue(). - self.capi_response_queue: Queue + self.capi_response_queue: Queue = Queue() logger.debug('Starting CAPI queries thread...') self.capi_query_thread = threading.Thread( target=self.capi_query_worker, @@ -613,10 +612,6 @@ class Session(object): self.capi_query_thread.start() logger.debug('Done') - def set_capi_response_queue(self, capi_response_queue: Queue) -> None: - """Set a reference to the CAPI response data queue.""" - self.capi_response_queue = capi_response_queue - def set_tk_master(self, master: tk.Tk) -> None: """Set a reference to main UI Tk root window.""" self.tk_master = master From 027455984b816f7bb7ef01f9210da29b36aad9d5 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:53:04 +0100 Subject: [PATCH 196/316] CAPI: EDMCCAPIFailedRequest itself, not inside a tuple * Also str(e.args) when setting the message. --- companion.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/companion.py b/companion.py index 4315a57c..93337fea 100644 --- a/companion.py +++ b/companion.py @@ -894,14 +894,12 @@ class Session(object): except Exception as e: self.capi_response_queue.put( - ( - EDMCCAPIFailedRequest( - message=e.args, - exception=e, - query_time=query.query_time, - play_sound=query.play_sound, - auto_update=query.auto_update - ), + EDMCCAPIFailedRequest( + message=str(e.args), + exception=e, + query_time=query.query_time, + play_sound=query.play_sound, + auto_update=query.auto_update ) ) From 26caf76cf4ea11a917cab96e2fe4516f7e46b5c8 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 11:56:32 +0100 Subject: [PATCH 197/316] CAPI: Declare request/response queue member types --- companion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 93337fea..a49a1a38 100644 --- a/companion.py +++ b/companion.py @@ -597,12 +597,12 @@ class Session(object): self.capi_raw_data = CAPIDataRaw() # Cache of raw replies from CAPI service # Queue that holds requests for CAPI queries, the items should always # be EDMCCAPIRequest objects. - self.capi_query_queue: Queue = Queue() + self.capi_query_queue: Queue[Optional[EDMCCAPIRequest]] = Queue() # This queue is used to pass the result, possibly a failure, of CAPI # queries back to the requesting code (technically anything checking # this queue, but it should be either EDMarketConnector.AppWindow or # EDMC.py). Items may be EDMCCAPIResponse or EDMCCAPIFailedRequest. - self.capi_response_queue: Queue = Queue() + self.capi_response_queue: Queue[Union[EDMCCAPIResponse, EDMCCAPIFailedRequest]] = Queue() logger.debug('Starting CAPI queries thread...') self.capi_query_thread = threading.Thread( target=self.capi_query_worker, From 503658bf4ed09a56446275400ba32f2c8e4dba01 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 12:04:13 +0100 Subject: [PATCH 198/316] companion: Correct Session.profile(query_time) default --- companion.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/companion.py b/companion.py index a49a1a38..8d78b619 100644 --- a/companion.py +++ b/companion.py @@ -996,7 +996,7 @@ class Session(object): def profile( self, - query_time: int = int(time.time()), + query_time: int = 0, tk_response_event: Optional[str] = None, retrying: bool = False, play_sound: bool = False, auto_update: bool = False ) -> None: @@ -1009,6 +1009,9 @@ class Session(object): :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ + if query_time == 0: + query_time = int(time.time()) + self.query( self.FRONTIER_CAPI_PATH_PROFILE, query_time=query_time, tk_response_event=tk_response_event, retrying=retrying, From 40a8a19ce4cb21fcea332cb1eb586c7f3775830e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 12:14:33 +0100 Subject: [PATCH 199/316] CAPI: Use special EDMCCAPIRequest endpoint, not 'None' for worker shutdown --- companion.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/companion.py b/companion.py index 8d78b619..5528262c 100644 --- a/companion.py +++ b/companion.py @@ -537,6 +537,8 @@ class EDMCCAPIReturn: class EDMCCAPIRequest(EDMCCAPIReturn): """Encapsulates a request for CAPI data.""" + REQUEST_WORKER_SHUTDOWN = '__EDMC_WORKER_SHUTDOWN' + def __init__( self, endpoint: str, query_time: int, tk_response_event: Optional[str] = None, retrying: bool = False, @@ -597,7 +599,7 @@ class Session(object): self.capi_raw_data = CAPIDataRaw() # Cache of raw replies from CAPI service # Queue that holds requests for CAPI queries, the items should always # be EDMCCAPIRequest objects. - self.capi_query_queue: Queue[Optional[EDMCCAPIRequest]] = Queue() + self.capi_request_queue: Queue[EDMCCAPIRequest] = Queue() # This queue is used to pass the result, possibly a failure, of CAPI # queries back to the requesting code (technically anything checking # this queue, but it should be either EDMarketConnector.AppWindow or @@ -876,15 +878,14 @@ class Session(object): return station_data while True: - query = self.capi_query_queue.get() + query = self.capi_request_queue.get() if not isinstance(query, EDMCCAPIRequest): - if query is not None: - logger.error("Item from queue wasn't an EDMCCAPIRequest") - break + logger.error("Item from queue wasn't an EDMCCAPIRequest") + break - else: - logger.info('Empty queue message, exiting...') - break + if query.endpoint == query.REQUEST_WORKER_SHUTDOWN: + logger.info(f'endpoint {query.REQUEST_WORKER_SHUTDOWN}, exiting...') + break logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') capi_data: CAPIData @@ -949,7 +950,12 @@ class Session(object): def capi_query_close_worker(self) -> None: """Ask the CAPI query thread to finish.""" - self.capi_query_queue.put(None) + self.capi_request_queue.put( + EDMCCAPIRequest( + endpoint=EDMCCAPIRequest.REQUEST_WORKER_SHUTDOWN, + query_time=int(time.time()) + ) + ) def query( self, endpoint: str, query_time: int, @@ -983,7 +989,7 @@ class Session(object): if conf_module.capi_pretend_down: raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') - self.capi_query_queue.put( + self.capi_request_queue.put( EDMCCAPIRequest( endpoint=endpoint, tk_response_event=tk_response_event, @@ -1032,7 +1038,7 @@ class Session(object): :param auto_update: Whether this request was triggered automatically. """ # Ask the thread worker to perform all three queries - self.capi_query_queue.put( + self.capi_request_queue.put( EDMCCAPIRequest( endpoint=self._CAPI_PATH_STATION, tk_response_event=tk_response_event, From a48c33eda873e596d5922f874a50b23c7415e0c4 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 13:45:42 +0100 Subject: [PATCH 200/316] companion: Change record_endpoint() query_time to mandatory This *does* need to be generated as close as possible to where the data was received, and not rely on an actually not dynamic default. --- companion.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 5528262c..fe112f5a 100644 --- a/companion.py +++ b/companion.py @@ -150,7 +150,7 @@ class CAPIDataRaw: def record_endpoint( self, endpoint: str, raw_data: str, - query_time: datetime.datetime = datetime.datetime.utcnow() + query_time: datetime.datetime ): """Record the latest raw data for the given endpoint.""" self.raw_data[endpoint] = CAPIDataRawEndpoint(raw_data, query_time) @@ -740,7 +740,10 @@ class Session(object): # May also fail here if token expired since response is empty capi_json = r.json() capi_data = CAPIData(capi_json, capi_endpoint) - self.capi_raw_data.record_endpoint(capi_endpoint, r.content.decode(encoding='utf-8')) + self.capi_raw_data.record_endpoint( + capi_endpoint, r.content.decode(encoding='utf-8'), + datetime.datetime.utcnow() + ) except requests.ConnectionError as e: logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {capi_endpoint})') From 6089dc6b0e815d0fe80b3b3a75640e7e14039702 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 13:48:32 +0100 Subject: [PATCH 201/316] companion: Optional[bool], not Union & minor `if` formatting --- companion.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/companion.py b/companion.py index fe112f5a..11b0b1a0 100644 --- a/companion.py +++ b/companion.py @@ -629,7 +629,7 @@ class Session(object): self.session.headers['User-Agent'] = USER_AGENT self.state = Session.STATE_OK - def login(self, cmdr: str = None, is_beta: Union[None, bool] = None) -> bool: + def login(self, cmdr: str = None, is_beta: Optional[bool] = None) -> bool: """ Attempt oAuth2 login. @@ -830,8 +830,10 @@ class Session(object): logger.error("No lastStarport in data!") return station_data - if ((last_starport_name := last_starport.get('name')) is None - or last_starport_name == ''): + if ( + (last_starport_name := last_starport.get('name')) is None + or last_starport_name == '' + ): # This could well be valid if you've been out exploring for a long # time. logger.warning("No lastStarport name!") From 7e1e35fd48547b985d8f059ba65f7523fb3c27b7 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 13:49:25 +0100 Subject: [PATCH 202/316] companion: Don't `self.capi_response_queue.put()` a tuple --- companion.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/companion.py b/companion.py index 11b0b1a0..a1d7b391 100644 --- a/companion.py +++ b/companion.py @@ -925,14 +925,12 @@ class Session(object): except Exception as e: self.capi_response_queue.put( - ( - EDMCCAPIFailedRequest( - message=e.args, - exception=e, - query_time=query.query_time, - play_sound=query.play_sound, - auto_update=query.auto_update - ), + EDMCCAPIFailedRequest( + message=e.args, + exception=e, + query_time=query.query_time, + play_sound=query.play_sound, + auto_update=query.auto_update ) ) From 120414ac59bb8ca2f1326f64a48cb4941e3a0267 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 14:08:36 +0100 Subject: [PATCH 203/316] CAPI: Convert StatsDialog.showstats() to using cached CAPI data This will just bail if no data has yet been received for /profile, but that's the same behaviour as if we don't yet have `monitor.cmdr`. --- companion.py | 8 ++++++++ stats.py | 37 ++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/companion.py b/companion.py index a1d7b391..a6e80345 100644 --- a/companion.py +++ b/companion.py @@ -167,6 +167,14 @@ class CAPIDataRaw: return capi_data_str + def __iter__(self): + """Make this iterable on its raw_data dict.""" + yield from self.raw_data + + def __getitem__(self, item): + """Make the raw_data dict's items get'able.""" + return self.raw_data.get(item) + def listify(thing: Union[List, Dict]) -> List: """ diff --git a/stats.py b/stats.py index 74f28e9c..89e79d9a 100644 --- a/stats.py +++ b/stats.py @@ -1,5 +1,6 @@ """CMDR Status information.""" import csv +import json import tkinter import tkinter as tk from sys import platform @@ -276,46 +277,40 @@ class StatsDialog(): return # TODO: This needs to use cached data - # LANG: Fetching data from Frontier CAPI in order to display on File > Status - self.status['text'] = _('Fetching data...') - self.parent.update_idletasks() - - try: - # TODO: This should use cached data - data = companion.session.profile() - - except companion.ServerError as e: - self.status['text'] = str(e) + if companion.session.FRONTIER_CAPI_PATH_PROFILE not in companion.session.capi_raw_data: + logger.info('No cached data, aborting...') return - except Exception as e: - logger.exception("error while attempting to show status") - self.status['text'] = str(e) - return + capi_data = json.loads( + companion.session.capi_raw_data[companion.session.FRONTIER_CAPI_PATH_PROFILE].raw_data + ) - if not data.get('commander') or not data['commander'].get('name', '').strip(): + if not capi_data.get('commander') or not capi_data['commander'].get('name', '').strip(): # Shouldn't happen # LANG: Unknown commander self.status['text'] = _("Who are you?!") elif ( - not data.get('lastSystem') - or not data['lastSystem'].get('name', '').strip() - or not data.get('lastStarport') - or not data['lastStarport'].get('name', '').strip() + not capi_data.get('lastSystem') + or not capi_data['lastSystem'].get('name', '').strip() + or not capi_data.get('lastStarport') + or not capi_data['lastStarport'].get('name', '').strip() ): # Shouldn't happen # LANG: Unknown location self.status['text'] = _("Where are you?!") - elif not data.get('ship') or not data['ship'].get('modules') or not data['ship'].get('name', '').strip(): + elif ( + not capi_data.get('ship') or not capi_data['ship'].get('modules') + or not capi_data['ship'].get('name', '').strip() + ): # Shouldn't happen # LANG: Unknown ship self.status['text'] = _("What are you flying?!") else: self.status['text'] = '' - StatsResults(self.parent, data) + StatsResults(self.parent, capi_data) class StatsResults(tk.Toplevel): From 05a95fbac446168fe08624706a56d9c4cf23cda6 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 14:23:14 +0100 Subject: [PATCH 204/316] companion: Update WTF comment about login/beta This is probably a left-over from the days of email+password login, not wanting to send that to anything but live CAPI server ? --- companion.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/companion.py b/companion.py index a6e80345..aa305b9f 100644 --- a/companion.py +++ b/companion.py @@ -648,6 +648,8 @@ class Session(object): raise CredentialsError('cannot login without a valid Client ID') # TODO: WTF is the intent behind this logic ? + # Perhaps to do with not even trying to auth if we're not sure if + # it's beta, but that's moot for *auth* since oAuth2. if not cmdr or is_beta is None: # Use existing credentials if not self.credentials: From 2065721dd9b5c59766eb9373ffa78e125c090168 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 14:47:15 +0100 Subject: [PATCH 205/316] CAPI: Store request `retrying` in companion.session.retrying The code was already referencing the latter, just we weren't setting it from the request yet. --- companion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/companion.py b/companion.py index aa305b9f..2d6d6036 100644 --- a/companion.py +++ b/companion.py @@ -903,6 +903,7 @@ class Session(object): break logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') + self.retrying = query.retrying capi_data: CAPIData if query.endpoint == self._CAPI_PATH_STATION: try: From baac45f5fe7c793dd31467b7053798c7011d4bd7 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 14:54:53 +0100 Subject: [PATCH 206/316] CAPI: Some extra comments for fail/auth mess --- companion.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/companion.py b/companion.py index 2d6d6036..76ac3240 100644 --- a/companion.py +++ b/companion.py @@ -762,10 +762,13 @@ class Session(object): except (requests.HTTPError, ValueError) as e: logger.exception('Frontier CAPI Auth: GET ') self.dump(r) + # TODO: Does this *necessarily* mean we need to call close() ? + # Maybe the **CAPI** had a transitory issue and auth is fine. self.close() if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication logger.error('Frontier CAPI Auth: query failed after refresh') + # TODO: Again, this might be an auth issue, but maybe not. self.invalidate() self.retrying = False # TODO: This must NOT happen here, we need to signal back to the requesting From 303df05f2cce678c366ea777c3c77ed5e8536d9c Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 25 Aug 2021 14:57:08 +0100 Subject: [PATCH 207/316] CAPI: Adjust 'requests.ConnectionError' logging It was specifically implying DNS resolution issues, but this is far more general than that. --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index 76ac3240..50163125 100644 --- a/companion.py +++ b/companion.py @@ -756,7 +756,7 @@ class Session(object): ) except requests.ConnectionError as e: - logger.warning(f'Unable to resolve name for CAPI: {e} (for request: {capi_endpoint})') + logger.warning(f'Request {capi_endpoint}: {e}') raise ServerConnectionError(f'Unable to connect to endpoint: {capi_endpoint}') from e except (requests.HTTPError, ValueError) as e: From 816384f16fe41174083df9add81eb714e1346d6d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 14:44:43 +0100 Subject: [PATCH 208/316] CAPI: Begin taking auth retries out of CAPI queries * Only set/use retrying as companion.session.retrying (untested). * Don't call Session.login() from CAPI query code, raise CredentialsError instead. * Also don't Session.close() in CAPI query code. --- EDMarketConnector.py | 13 ++++++----- companion.py | 51 +++++++++++++++++++------------------------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b82bfe98..6fef5579 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -891,12 +891,14 @@ class AppWindow(object): return True - def capi_request_data(self, event=None, retrying: bool = False): + def capi_request_data(self, event=None) -> None: """ Perform CAPI data retrieval and associated actions. This can be triggered by hitting the main UI 'Update' button, automatically on docking, or due to a retry. + + :param event: Tk generated event details. """ auto_update = not event play_sound = (auto_update or int(event.type) == self.EVENT_VIRTUAL) and not config.get_int('hotkey_mute') @@ -916,7 +918,7 @@ class AppWindow(object): self.login() return - if not retrying: + if not companion.session.retrying: if time() < self.capi_query_holdoff_time: # Was invoked by key while in cooldown if play_sound and (self.capi_query_holdoff_time - time()) < companion.capi_query_cooldown * 0.75: self.status['text'] = '' @@ -937,7 +939,7 @@ class AppWindow(object): config.set('querytime', query_time) companion.session.station( query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, - retrying=retrying, play_sound=play_sound + play_sound=play_sound ) def capi_handle_response(self, event=None): # noqa: C901, CCR001 @@ -1112,13 +1114,14 @@ class AppWindow(object): # Companion API problem except companion.ServerLagging as e: err = str(e) - if capi_response.retrying: + if companion.session.retrying: self.status['text'] = err play_bad = True else: # Retry once if Companion server is unresponsive - self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event, True)) + companion.session.retrying = True + self.w.after(int(SERVER_RETRY * 1000), lambda: self.capi_request_data(event)) return # early exit to avoid starting cooldown count except companion.CmdrError as e: # Companion API return doesn't match Journal diff --git a/companion.py b/companion.py index 50163125..90d49579 100644 --- a/companion.py +++ b/companion.py @@ -533,11 +533,10 @@ class EDMCCAPIReturn: def __init__( self, query_time: int, tk_response_event: Optional[str] = None, - retrying: bool = False, play_sound: bool = False, auto_update: bool = False + play_sound: bool = False, auto_update: bool = False ): self.tk_response_event = tk_response_event # Name of tk event to generate when response queued. self.query_time: int = query_time # When this query is considered to have started (time_t). - self.retrying: bool = retrying # Whether this is already a retry. self.play_sound: bool = play_sound # Whether to play good/bad sounds for success/failure. self.auto_update: bool = auto_update # Whether this was automatically triggered. @@ -549,11 +548,11 @@ class EDMCCAPIRequest(EDMCCAPIReturn): def __init__( self, endpoint: str, query_time: int, - tk_response_event: Optional[str] = None, retrying: bool = False, + tk_response_event: Optional[str] = None, play_sound: bool = False, auto_update: bool = False ): super().__init__( - query_time=query_time, tk_response_event=tk_response_event, retrying=retrying, + query_time=query_time, tk_response_event=tk_response_event, play_sound=play_sound, auto_update=auto_update ) self.endpoint: str = endpoint # The CAPI query to perform. @@ -564,9 +563,9 @@ class EDMCCAPIResponse(EDMCCAPIReturn): def __init__( self, capi_data: CAPIData, - query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False + query_time: int, play_sound: bool = False, auto_update: bool = False ): - super().__init__(query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + super().__init__(query_time=query_time, play_sound=play_sound, auto_update=auto_update) self.capi_data: CAPIData = capi_data # Frontier CAPI response, possibly augmented (station query) @@ -575,10 +574,10 @@ class EDMCCAPIFailedRequest(EDMCCAPIReturn): def __init__( self, message: str, - query_time: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False, + query_time: int, play_sound: bool = False, auto_update: bool = False, exception=None ): - super().__init__(query_time=query_time, retrying=retrying, play_sound=play_sound, auto_update=auto_update) + super().__init__(query_time=query_time, play_sound=play_sound, auto_update=auto_update) self.message: str = message # User-friendly reason for failure. self.exception: int = exception # Exception that recipient should raise. @@ -759,21 +758,16 @@ class Session(object): logger.warning(f'Request {capi_endpoint}: {e}') raise ServerConnectionError(f'Unable to connect to endpoint: {capi_endpoint}') from e - except (requests.HTTPError, ValueError) as e: - logger.exception('Frontier CAPI Auth: GET ') + except requests.HTTPError as e: # In response to raise_for_status() + logger.exception(f'Frontier CAPI Auth: GET {capi_endpoint}') self.dump(r) - # TODO: Does this *necessarily* mean we need to call close() ? - # Maybe the **CAPI** had a transitory issue and auth is fine. - self.close() + + if r.status_code == 401: # CAPI doesn't think we're Auth'd + # TODO: Translation ? + raise CredentialsError('Frontier CAPI said Auth required') from e if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication - logger.error('Frontier CAPI Auth: query failed after refresh') - # TODO: Again, this might be an auth issue, but maybe not. - self.invalidate() self.retrying = False - # TODO: This must NOT happen here, we need to signal back to the requesting - # code that it needs to sort out auth. - self.login() raise CredentialsError('query failed after refresh') from e # TODO: Better to return error and have upstream re-try auth ? @@ -788,6 +782,11 @@ class Session(object): logger.error('Frontier CAPI Auth: HTTP error or invalid JSON') raise CredentialsError('HTTP error or invalid JSON') from e + except ValueError as e: + logger.exception(f'decoding CAPI response content:\n{r.content.decode(encoding="utf-8")}\n') + # TODO: What now ? + raise ServerError("Couldn't JSON decode CAPI response") from e + except Exception as e: logger.debug('Attempting GET', exc_info=e) # LANG: Frontier CAPI data retrieval failed @@ -906,7 +905,6 @@ class Session(object): break logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') - self.retrying = query.retrying capi_data: CAPIData if query.endpoint == self._CAPI_PATH_STATION: try: @@ -976,7 +974,7 @@ class Session(object): def query( self, endpoint: str, query_time: int, - tk_response_event: Optional[str] = None, retrying: bool = False, + tk_response_event: Optional[str] = None, play_sound: bool = False, auto_update: bool = False ) -> None: """ @@ -985,7 +983,6 @@ class Session(object): :param endpoint: The CAPI endpoint to query. :param tk_response_event: Name of tk event to generate when response queued. :param query_time: When this query was initiated. - :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ @@ -1010,7 +1007,6 @@ class Session(object): EDMCCAPIRequest( endpoint=endpoint, tk_response_event=tk_response_event, - retrying=retrying, query_time=query_time, play_sound=play_sound, auto_update=auto_update @@ -1020,7 +1016,7 @@ class Session(object): def profile( self, query_time: int = 0, - tk_response_event: Optional[str] = None, retrying: bool = False, + tk_response_event: Optional[str] = None, play_sound: bool = False, auto_update: bool = False ) -> None: """ @@ -1028,7 +1024,6 @@ class Session(object): :param query_time: When this query was initiated. :param tk_response_event: Name of tk event to generate when response queued. - :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ @@ -1037,20 +1032,19 @@ class Session(object): self.query( self.FRONTIER_CAPI_PATH_PROFILE, query_time=query_time, - tk_response_event=tk_response_event, retrying=retrying, + tk_response_event=tk_response_event, play_sound=play_sound, auto_update=auto_update ) def station( self, query_time: int, tk_response_event: Optional[str] = None, - retrying: bool = False, play_sound: bool = False, auto_update: bool = False + play_sound: bool = False, auto_update: bool = False ) -> None: """ Perform CAPI quer(y|ies) for station data. :param query_time: When this query was initiated. :param tk_response_event: Name of tk event to generate when response queued. - :param retrying: Whether this is a retry. :param play_sound: Whether the app should play a sound on error. :param auto_update: Whether this request was triggered automatically. """ @@ -1060,7 +1054,6 @@ class Session(object): endpoint=self._CAPI_PATH_STATION, tk_response_event=tk_response_event, query_time=query_time, - retrying=retrying, play_sound=play_sound, auto_update=auto_update ) From a53858750c1606f34b31cbff285001064d24942e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 14:52:47 +0100 Subject: [PATCH 209/316] CAPI: query errors: Continue taking auth-related reactions out --- companion.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/companion.py b/companion.py index 90d49579..b1f76ae8 100644 --- a/companion.py +++ b/companion.py @@ -766,21 +766,18 @@ class Session(object): # TODO: Translation ? raise CredentialsError('Frontier CAPI said Auth required') from e - if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication - self.retrying = False - raise CredentialsError('query failed after refresh') from e + if r.status_code == 418: # "I'm a teapot" - used to signal maintenance + # TODO: Translation ? + raise ServerError("Frontier CAPI down for maintenance") from e + + # TODO: Let caller decide on this + # if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication + # self.retrying = False + # raise CredentialsError('query failed after refresh') from e # TODO: Better to return error and have upstream re-try auth ? - elif self.login(): # Maybe our token expired. Re-authorize in any case - logger.debug('Initial query failed, but login() just worked, trying again...') - self.retrying = True - # TODO: This, or raise (custom?) exception for upstream to do it? - return capi_single_query(capi_endpoint) - - else: - self.retrying = False - logger.error('Frontier CAPI Auth: HTTP error or invalid JSON') - raise CredentialsError('HTTP error or invalid JSON') from e + logger.exception('Frontier CAPI: Misc. Error') + raise ServerError('Frontier CAPI: Misc. Error') from e except ValueError as e: logger.exception(f'decoding CAPI response content:\n{r.content.decode(encoding="utf-8")}\n') From 0637873b9128ab027fd15fa08638d1c6f3835162 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 14:54:01 +0100 Subject: [PATCH 210/316] CAPI: query error - Change JSON decode error message --- companion.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/companion.py b/companion.py index b1f76ae8..aacb0735 100644 --- a/companion.py +++ b/companion.py @@ -781,8 +781,7 @@ class Session(object): except ValueError as e: logger.exception(f'decoding CAPI response content:\n{r.content.decode(encoding="utf-8")}\n') - # TODO: What now ? - raise ServerError("Couldn't JSON decode CAPI response") from e + raise ServerError("Frontier CAPI response: couldn't decode JSON") from e except Exception as e: logger.debug('Attempting GET', exc_info=e) From 885745197c1330da570ff5917420b821ec5cf7ef Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 15:00:16 +0100 Subject: [PATCH 211/316] CAPI: EDMarketConnector: Set status text on ServerConnectionError --- EDMarketConnector.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 6fef5579..67d9064d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1105,6 +1105,9 @@ class AppWindow(object): # TODO: Set status text return + except companion.ServerConnectionError: + self.status['text'] = _('Frontier CAPI server error') + except companion.CredentialsError: # Redirected back to Auth server - force full re-authentication companion.session.invalidate() From 6c24d9291ae0d7c06ffc89edf29590c65b4d5a1e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 15:13:33 +0100 Subject: [PATCH 212/316] CAPI: EDMarketConnector: Don't Session.invalidate() on CredentialsError --- EDMarketConnector.py | 2 -- companion.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 67d9064d..c2d3621d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1109,8 +1109,6 @@ class AppWindow(object): self.status['text'] = _('Frontier CAPI server error') except companion.CredentialsError: - # Redirected back to Auth server - force full re-authentication - companion.session.invalidate() companion.session.retrying = False companion.session.login() diff --git a/companion.py b/companion.py index aacb0735..41640ca5 100644 --- a/companion.py +++ b/companion.py @@ -788,11 +788,13 @@ class Session(object): # LANG: Frontier CAPI data retrieval failed raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e + # TODO: I don't think this happens any more, it's just a 401 status as above if r.url.startswith(FRONTIER_AUTH_SERVER): logger.info('Redirected back to Auth Server') self.dump(r) raise CredentialsError('Redirected back to Auth Server') + # TODO: Shouldn't this be covered by raise_for_status() above ? elif 500 <= r.status_code < 600: # Server error. Typically 500 "Internal Server Error" if server is down logger.debug('500 status back from CAPI') From 2179936d6f9154bdc1c599b5fd685c8d6f195ef4 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 15:14:52 +0100 Subject: [PATCH 213/316] CAPI: No need to handle HTTP 5xx specially This will be handled by the `except HTTPError` above. --- companion.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/companion.py b/companion.py index 41640ca5..bb1af88a 100644 --- a/companion.py +++ b/companion.py @@ -794,14 +794,6 @@ class Session(object): self.dump(r) raise CredentialsError('Redirected back to Auth Server') - # TODO: Shouldn't this be covered by raise_for_status() above ? - elif 500 <= r.status_code < 600: - # Server error. Typically 500 "Internal Server Error" if server is down - logger.debug('500 status back from CAPI') - self.dump(r) - # LANG: Frontier CAPI data retrieval failed with 5XX code - raise ServerError(f'{_("Frontier CAPI server error")}: {r.status_code}') - self.retrying = False if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data: From cd6d22844b4e7a2aeb758a0dacf764101782d8bc Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 15:15:36 +0100 Subject: [PATCH 214/316] CAPI: 'Need auth' is from 401 now, not a redirect --- companion.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/companion.py b/companion.py index bb1af88a..523ff89d 100644 --- a/companion.py +++ b/companion.py @@ -734,7 +734,7 @@ class Session(object): """Worker thread that performs actual CAPI queries.""" logger.debug('CAPI worker thread starting') - def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 + def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: """ Perform a *single* CAPI endpoint query within the thread worker. @@ -788,12 +788,6 @@ class Session(object): # LANG: Frontier CAPI data retrieval failed raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e - # TODO: I don't think this happens any more, it's just a 401 status as above - if r.url.startswith(FRONTIER_AUTH_SERVER): - logger.info('Redirected back to Auth Server') - self.dump(r) - raise CredentialsError('Redirected back to Auth Server') - self.retrying = False if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data: From ef32eb01402f8323aba6ab9234bfae34545b4951 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 15:16:42 +0100 Subject: [PATCH 215/316] CAPI: Remove un-needed check/setting of Session.retrying --- companion.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/companion.py b/companion.py index 523ff89d..4950b74e 100644 --- a/companion.py +++ b/companion.py @@ -770,12 +770,6 @@ class Session(object): # TODO: Translation ? raise ServerError("Frontier CAPI down for maintenance") from e - # TODO: Let caller decide on this - # if self.retrying: # Refresh just succeeded but this query failed! Force full re-authentication - # self.retrying = False - # raise CredentialsError('query failed after refresh') from e - - # TODO: Better to return error and have upstream re-try auth ? logger.exception('Frontier CAPI: Misc. Error') raise ServerError('Frontier CAPI: Misc. Error') from e @@ -788,8 +782,6 @@ class Session(object): # LANG: Frontier CAPI data retrieval failed raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e - self.retrying = False - if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data: logger.error('No commander in returned data') From 206367505c4ea0e12989b146e67d50589929af08 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 26 Aug 2021 15:31:39 +0100 Subject: [PATCH 216/316] CAPI: Cleanup some (un)needed translations --- companion.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/companion.py b/companion.py index 4950b74e..f69ae122 100644 --- a/companion.py +++ b/companion.py @@ -763,12 +763,13 @@ class Session(object): self.dump(r) if r.status_code == 401: # CAPI doesn't think we're Auth'd - # TODO: Translation ? + # No need for translation, we'll go straight into trying new Auth + # and thus any message would be overwritten. raise CredentialsError('Frontier CAPI said Auth required') from e if r.status_code == 418: # "I'm a teapot" - used to signal maintenance - # TODO: Translation ? - raise ServerError("Frontier CAPI down for maintenance") from e + # LANG: Frontier CAPI returned 418, meaning down for maintenance + raise ServerError(_("Frontier CAPI down for maintenance")) from e logger.exception('Frontier CAPI: Misc. Error') raise ServerError('Frontier CAPI: Misc. Error') from e @@ -790,8 +791,6 @@ class Session(object): '%Y-%m-%dT%H:%M:%SZ', parsedate(r.headers['Date']) # type: ignore ) - # TODO: Store a copy of this if it was /profile, e.g. for use by - # stats.py return capi_data def capi_station_queries(timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 From 12b1e9b7bc2782042968859e01bb59b16232da25 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 13:58:46 +0100 Subject: [PATCH 217/316] CAPI: Add more trace_if('capi.worker', ...) to check timings I keep observing it take 3-4s to update the UI after a manual CAPI request. This showed up as mostly *in* AppWindow.capi_handle_response(), but the moment I ran it under PyCharm profile that went down to 400ms. --- EDMarketConnector.py | 5 +++++ companion.py | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index c2d3621d..425550ce 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -900,6 +900,7 @@ class AppWindow(object): :param event: Tk generated event details. """ + logger.trace_if('capi.worker', 'Begin') auto_update = not event play_sound = (auto_update or int(event.type) == self.EVENT_VIRTUAL) and not config.get_int('hotkey_mute') @@ -937,6 +938,7 @@ class AppWindow(object): query_time = int(time()) logger.trace_if('capi.worker', 'Requesting full station data') config.set('querytime', query_time) + logger.trace_if('capi.worker', 'Calling companion.session.station') companion.session.station( query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, play_sound=play_sound @@ -944,6 +946,7 @@ class AppWindow(object): def capi_handle_response(self, event=None): # noqa: C901, CCR001 """Handle the resulting data from a CAPI query.""" + logger.trace_if('capi.worker', 'Handling response') play_bad: bool = False err: Optional[str] = None @@ -1151,9 +1154,11 @@ class AppWindow(object): # Update Odyssey Suit data companion.session.suit_update(capi_response.capi_data) + logger.trace_if('capi.worker', 'Updating suit and cooldown...') self.update_suit_text() self.suit_show_if_set() self.cooldown() + logger.trace_if('capi.worker', '...done') def journal_event(self, event): # noqa: C901, CCR001 # Currently not easily broken up. """ diff --git a/companion.py b/companion.py index f69ae122..11ee0a5b 100644 --- a/companion.py +++ b/companion.py @@ -734,7 +734,7 @@ class Session(object): """Worker thread that performs actual CAPI queries.""" logger.debug('CAPI worker thread starting') - def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: + def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 """ Perform a *single* CAPI endpoint query within the thread worker. @@ -744,7 +744,9 @@ class Session(object): """ capi_data: CAPIData try: + logger.trace_if('capi.worker', 'Sending HTTP request...') r = self.session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore + logger.trace_if('capi.worker', '... got result...') r.raise_for_status() # Typically 403 "Forbidden" on token expiry # May also fail here if token expired since response is empty capi_json = r.json() @@ -871,6 +873,7 @@ class Session(object): while True: query = self.capi_request_queue.get() + logger.trace_if('capi.worker', 'De-queued request') if not isinstance(query, EDMCCAPIRequest): logger.error("Item from queue wasn't an EDMCCAPIRequest") break @@ -934,6 +937,7 @@ class Session(object): # If the query came from EDMC.(py|exe) there's no tk to send an # event too, so assume it will be polling there response queue. if query.tk_response_event is not None: + logger.trace_if('capi.worker', 'Sending <<CAPIResponse>>') self.tk_master.event_generate('<<CAPIResponse>>') logger.info('CAPI worker thread DONE') @@ -1024,6 +1028,7 @@ class Session(object): :param auto_update: Whether this request was triggered automatically. """ # Ask the thread worker to perform all three queries + logger.trace_if('capi.worker', 'Enqueueing request') self.capi_request_queue.put( EDMCCAPIRequest( endpoint=self._CAPI_PATH_STATION, From bc29891cc12fbfb82737f1b764162df510ff7edd Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 14:14:56 +0100 Subject: [PATCH 218/316] CAPI: Some notes in comments --- EDMarketConnector.py | 1 + companion.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 425550ce..aa6ed380 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1113,6 +1113,7 @@ class AppWindow(object): except companion.CredentialsError: companion.session.retrying = False + # TODO: Might need to .invalidate() here to avoid "already auth'd" companion.session.login() # Companion API problem diff --git a/companion.py b/companion.py index 11ee0a5b..c2770ca0 100644 --- a/companion.py +++ b/companion.py @@ -749,6 +749,8 @@ class Session(object): logger.trace_if('capi.worker', '... got result...') r.raise_for_status() # Typically 403 "Forbidden" on token expiry # May also fail here if token expired since response is empty + # r.status_code = 401 + # raise requests.HTTPError capi_json = r.json() capi_data = CAPIData(capi_json, capi_endpoint) self.capi_raw_data.record_endpoint( From 6968cd6c69cc72e6b692c744fef47adfa7d28c6b Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 14:18:49 +0100 Subject: [PATCH 219/316] companion: Rename Auth.session to requests_session So as to not confuse with companion.session. --- Contributing.md | 4 ++-- companion.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Contributing.md b/Contributing.md index c74953a1..bdbf4634 100644 --- a/Contributing.md +++ b/Contributing.md @@ -247,10 +247,10 @@ from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT TARGET_URL = 'https://www.edsm.net/api-journal-v1' if 'edsm' in debug_senders: - TARGET_URL = f'http://{DEBUG_WEBSERVER_HOST}:{DEBUG_WEBSERVER_PORT}/edsm' + TARGET_URL = f'http://{DEBUG_WEBSERVER_HOST}:{DEBUG_WEBSERVER_PORT}/edsm' ... - r = this.session.post(TARGET_URL, data=data, timeout=_TIMEOUT) +r = this.requests_session.post(TARGET_URL, data=data, timeout=_TIMEOUT) ``` Be sure to set a URL path in the `TARGET_URL` that denotes where the data diff --git a/companion.py b/companion.py index c2770ca0..bb5d3605 100644 --- a/companion.py +++ b/companion.py @@ -295,15 +295,15 @@ class Auth(object): def __init__(self, cmdr: str) -> None: self.cmdr: str = cmdr - self.session = requests.Session() - self.session.headers['User-Agent'] = USER_AGENT + self.requests_session = requests.Session() + self.requests_session.headers['User-Agent'] = USER_AGENT self.verifier: Union[bytes, None] = None self.state: Union[str, None] = None def __del__(self) -> None: """Ensure our Session is closed if we're being deleted.""" - if self.session: - self.session.close() + if self.requests_session: + self.requests_session.close() def refresh(self) -> Optional[str]: """ @@ -334,7 +334,7 @@ class Auth(object): logger.debug('Attempting refresh with Frontier...') try: - r = self.session.post( + r = self.requests_session.post( FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, data=data, timeout=auth_timeout @@ -422,7 +422,7 @@ class Auth(object): # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True - r = self.session.post( + r = self.requests_session.post( FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_TOKEN, data=request_data, timeout=auth_timeout @@ -430,7 +430,7 @@ class Auth(object): data_token = r.json() if r.status_code == requests.codes.ok: # Now we need to /decode the token to check the customer_id against FID - r = self.session.get( + r = self.requests_session.get( FRONTIER_AUTH_SERVER + self.FRONTIER_AUTH_PATH_DECODE, headers={ 'Authorization': f'Bearer {data_token.get("access_token", "")}', From 74170da4347c191f393828e5502ac4ab2558b9bb Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 14:24:15 +0100 Subject: [PATCH 220/316] companion: Rename Session.session to requests_session So as to avoid confusion with companion.session --- companion.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/companion.py b/companion.py index bb5d3605..3288cd74 100644 --- a/companion.py +++ b/companion.py @@ -598,7 +598,7 @@ class Session(object): self.state = Session.STATE_INIT self.server: Optional[str] = None self.credentials: Optional[Dict[str, Any]] = None - self.session: Optional[requests.Session] = None + self.requests_session: Optional[requests.Session] = None self.auth: Optional[Auth] = None self.retrying = False # Avoid infinite loop when successful auth / unsuccessful query self.tk_master: Optional[tk.Tk] = None @@ -631,9 +631,9 @@ class Session(object): def start_frontier_auth(self, access_token: str) -> None: """Start an oAuth2 session.""" logger.debug('Starting session') - self.session = requests.Session() - self.session.headers['Authorization'] = f'Bearer {access_token}' - self.session.headers['User-Agent'] = USER_AGENT + self.requests_session = requests.Session() + self.requests_session.headers['Authorization'] = f'Bearer {access_token}' + self.requests_session.headers['User-Agent'] = USER_AGENT self.state = Session.STATE_OK def login(self, cmdr: str = None, is_beta: Optional[bool] = None) -> bool: @@ -710,14 +710,14 @@ class Session(object): def close(self) -> None: """Close Frontier authorization session.""" self.state = Session.STATE_INIT - if self.session: + if self.requests_session: try: - self.session.close() + self.requests_session.close() except Exception as e: logger.debug('Frontier Auth: closing', exc_info=e) - self.session = None + self.requests_session = None def invalidate(self) -> None: """Invalidate Frontier authorization credentials.""" @@ -745,7 +745,7 @@ class Session(object): capi_data: CAPIData try: logger.trace_if('capi.worker', 'Sending HTTP request...') - r = self.session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore + r = self.requests_session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore logger.trace_if('capi.worker', '... got result...') r.raise_for_status() # Typically 403 "Forbidden" on token expiry # May also fail here if token expired since response is empty From 024e2ba35768e7e5fb524c269bc209ef2d4a54bd Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 14:24:54 +0100 Subject: [PATCH 221/316] EDMarketConnector: invalidate companion session and return on CredentialsError * If we don't invalidate then the companion.Auth code will do nothing due to "already auth'd". * We need to give the auth flow time to complete, so need to return. * As that auth flow can take indeterminate time, not setting a timed retry of the CAPI query here. We should consider setting a flag and reacting in the Auth code though. --- EDMarketConnector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index aa6ed380..9b02e912 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1114,7 +1114,9 @@ class AppWindow(object): except companion.CredentialsError: companion.session.retrying = False # TODO: Might need to .invalidate() here to avoid "already auth'd" + companion.session.invalidate() companion.session.login() + return # Companion API problem except companion.ServerLagging as e: From e3cd1d642993564e9a65fc2465830ee3954fb934 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 14:27:20 +0100 Subject: [PATCH 222/316] EDMarketConnector: Comment about no timed CAPI retry on re-Auth --- EDMarketConnector.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 9b02e912..d416c1b2 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1113,10 +1113,9 @@ class AppWindow(object): except companion.CredentialsError: companion.session.retrying = False - # TODO: Might need to .invalidate() here to avoid "already auth'd" companion.session.invalidate() companion.session.login() - return + return # We need to give Auth time to complete, so can't set a timed retry # Companion API problem except companion.ServerLagging as e: From 67220fc96a592d68bad5042494f1da5607849a83 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 14:35:22 +0100 Subject: [PATCH 223/316] CAPI: Change CAPIDataRaw.__getitem__ to using __getitem__ itself The only use of this is stats.py, and it explicitly checks for the key's existence first. --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index 3288cd74..f6301d07 100644 --- a/companion.py +++ b/companion.py @@ -173,7 +173,7 @@ class CAPIDataRaw: def __getitem__(self, item): """Make the raw_data dict's items get'able.""" - return self.raw_data.get(item) + return self.raw_data.__getitem__(item) def listify(thing: Union[List, Dict]) -> List: From 9e206d092cbc0e0006819f67edfc7e59645cfdf5 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 14:36:54 +0100 Subject: [PATCH 224/316] Fix "" string to be '' --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index d416c1b2..9c113b1f 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -964,7 +964,7 @@ class AppWindow(object): logger.trace_if('capi.worker', 'Answer is not a Failure') if not isinstance(capi_response, companion.EDMCCAPIResponse): - msg = f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}" + msg = f'Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}' logger.error(msg) raise ValueError(msg) From 50db6c528ada4ae94372acd7b46e59b2ed1b8ae0 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sat, 28 Aug 2021 15:34:41 +0100 Subject: [PATCH 225/316] EDMC: Attempt to handle CAPI exceptions as per EDMarketConnector.py * Moved most of the exception handling/message output down to the outermost try's except clauses. --- EDMC.py | 57 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/EDMC.py b/EDMC.py index ab65d726..6c7163d7 100755 --- a/EDMC.py +++ b/EDMC.py @@ -271,6 +271,7 @@ sys.path: {sys.path}''' companion.session.login(monitor.cmdr, monitor.is_beta) + ################################################################### # Initiate CAPI queries querytime = int(time()) companion.session.station(query_time=querytime) @@ -286,6 +287,8 @@ sys.path: {sys.path}''' logger.error(f'CAPI requests timed out after {_capi_request_timeout} seconds') sys.exit(EXIT_SERVER) + ################################################################### + # noinspection DuplicatedCode if isinstance(capi_response, companion.EDMCCAPIFailedRequest): logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}') @@ -297,9 +300,7 @@ sys.path: {sys.path}''' logger.trace_if('capi.worker', 'Answer is not a Failure') if not isinstance(capi_response, companion.EDMCCAPIResponse): - msg = f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}" - logger.error(msg) - raise ValueError(msg) + raise ValueError(f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}") data = capi_response.capi_data config.set('querytime', querytime) @@ -309,10 +310,10 @@ sys.path: {sys.path}''' logger.error("No data['command']['name'] from CAPI") sys.exit(EXIT_SERVER) - elif not deep_get(data, 'lastSystem', 'name') or \ - data['commander'].get('docked') and not \ - deep_get(data, 'lastStarport', 'name'): # Only care if docked - + elif ( + not deep_get(data, 'lastSystem', 'name') or + data['commander'].get('docked') and not deep_get(data, 'lastStarport', 'name') + ): # Only care if docked logger.error("No data['lastSystem']['name'] from CAPI") sys.exit(EXIT_SERVER) @@ -324,16 +325,15 @@ sys.path: {sys.path}''' pass # Skip further validation elif data['commander']['name'] != monitor.cmdr: - logger.error(f'Commander "{data["commander"]["name"]}" from CAPI doesn\'t match "{monitor.cmdr}" from Journal') # noqa: E501 - sys.exit(EXIT_CREDENTIALS) + raise companion.CmdrError() - elif data['lastSystem']['name'] != monitor.system or \ - ((data['commander']['docked'] and data['lastStarport']['name'] or None) != monitor.station) or \ - data['ship']['id'] != monitor.state['ShipID'] or \ - data['ship']['name'].lower() != monitor.state['ShipType']: - - logger.error('Mismatch(es) between CAPI and Journal for at least one of: StarSystem, Last Star Port, Ship ID or Ship Name/Type') # noqa: E501 - sys.exit(EXIT_LAGGING) + elif ( + data['lastSystem']['name'] != monitor.system or + ((data['commander']['docked'] and data['lastStarport']['name'] or None) != monitor.station) or + data['ship']['id'] != monitor.state['ShipID'] or + data['ship']['name'].lower() != monitor.state['ShipType'] + ): + raise companion.ServerLagging() # stuff we can do when not docked if args.d: @@ -446,13 +446,36 @@ sys.path: {sys.path}''' logger.exception('Failed to send data to EDDN') except companion.ServerError: - logger.error('Frontier CAPI Server returned an error') + logger.exception('Frontier CAPI Server returned an error') + sys.exit(EXIT_SERVER) + + except companion.ServerConnectionError: + logger.exception('Exception while contacting server') sys.exit(EXIT_SERVER) except companion.CredentialsError: logger.error('Frontier CAPI Server: Invalid Credentials') sys.exit(EXIT_CREDENTIALS) + # Companion API problem + except companion.ServerLagging: + logger.error( + 'Mismatch(es) between CAPI and Journal for at least one of: ' + 'StarSystem, Last Star Port, Ship ID or Ship Name/Type' + ) + sys.exit(EXIT_SERVER) + + except companion.CmdrError: # Companion API return doesn't match Journal + logger.error( + f'Commander "{data["commander"]["name"]}" from CAPI doesn\'t match ' + f'"{monitor.cmdr}" from Journal' + ) + sys.exit(EXIT_SERVER) + + except Exception: + logger.exception('"other" exception') + sys.exit(EXIT_SERVER) + if __name__ == '__main__': main() From d1dbd9da8f790293ba02ac83224973fc7568161d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 30 Aug 2021 12:55:45 +0100 Subject: [PATCH 226/316] AppWindow.capi_request_data: Specific "We're aborting query" messages --- EDMarketConnector.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 9c113b1f..d0559345 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -904,14 +904,35 @@ class AppWindow(object): auto_update = not event play_sound = (auto_update or int(event.type) == self.EVENT_VIRTUAL) and not config.get_int('hotkey_mute') - if ( - not monitor.cmdr or not monitor.mode or monitor.state['Captain'] - or not monitor.system or monitor.mode == 'CQC' - ): - logger.trace_if('capi.worker', 'CQC detected, aborting query') + if not monitor.cmdr: + logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') + # LANG: CAPI queries aborted because Cmdr name is unknown + self.status['text'] = _('CAPI query aborted: Cmdr name unknown') + return + + if not monitor.mode: + logger.trace_if('capi.worker', 'Aborting Query: Game Mode unknown') + # LANG: CAPI queries aborted because game mode unknown + self.status['text'] = _('CAPI query aborted: Game mode unknown') + return + + if not monitor.system: + logger.trace_if('capi.worker', 'Aborting Query: Current star system unknown') + # LANG: CAPI queries aborted because current star system name unknown + self.status['text'] = _('CAPI query aborted: Current system unknown') + return + + if monitor.state['Captain']: + logger.trace_if('capi.worker', 'Aborting Query: In multi-crew') + # LANG: CAPI queries aborted because player is in multi-crew on other Cmdr's ship + self.status['text'] = _('CAPI query aborted: In other-ship multi-crew') + return + + if monitor.mode == 'CQC': + logger.trace_if('capi.worker', 'Aborting Query: In CQC') # LANG: CAPI queries aborted because player is in CQC (Arena) - self.status['text'] = _('CQC detected, aborting CAPI query') - return # In CQC or on crew - do nothing + self.status['text'] = _('CAPI query aborted: CQC (Arena) detected') + return if companion.session.state == companion.Session.STATE_AUTH: logger.trace_if('capi.worker', 'Auth in progress? Aborting query') From fd3ebc9c21e98717fb519e7c5ddff11b6800fd2d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 10:49:04 +0100 Subject: [PATCH 227/316] Python version: 3.9.7 --- .github/workflows/windows-build.yml | 2 +- .python-version | 2 +- ChangeLog.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index e1041864..eac27cf4 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2.3.4 - uses: actions/setup-python@v2.2.2 with: - python-version: "3.9.6" + python-version: "3.9.7" architecture: "x86" - name: Install python tools diff --git a/.python-version b/.python-version index 1635d0f5..f69abe41 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.9.6 +3.9.7 diff --git a/ChangeLog.md b/ChangeLog.md index e8031212..2eb45d0b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,7 +9,7 @@ produce the Windows executables and installer. --- -* We now test against, and package with, Python 3.9.6. +* We now test against, and package with, Python 3.9.7. **As a consequence of this we no longer support Windows 7. This is due to From 092b10447c9f2c8fe280200de8f0c63a1d273529 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 12:09:02 +0100 Subject: [PATCH 228/316] EDMarketConnector.py: Early return from AppWindow.save_raw() --- EDMarketConnector.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index d0559345..53260e6f 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1611,9 +1611,11 @@ class AppWindow(object): initialdir=config.get_str('outdir'), initialfile=f'{monitor.system}{monitor.station}.{timestamp}' ) - if f: - with open(f, 'wb') as h: - h.write(str(companion.session.capi_raw_data).encode(encoding='utf-8')) + if not f: + return + + with open(f, 'wb') as h: + h.write(str(companion.session.capi_raw_data).encode(encoding='utf-8')) # def exit_tray(self, systray: 'SysTrayIcon') -> None: # """Tray icon is shutting down.""" From 613a992ae9a4d006d2b6f907e80660b556d7d738 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 12:09:40 +0100 Subject: [PATCH 229/316] companion: Change comments on CAPI cooldown/timeouts --- companion.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index f6301d07..8524b510 100644 --- a/companion.py +++ b/companion.py @@ -48,8 +48,8 @@ else: # Define custom type for the dicts that hold CAPI data # CAPIData = NewType('CAPIData', Dict) -capi_query_cooldown = 60 # be nice -capi_default_timeout = 10 # requests timeout +capi_query_cooldown = 60 # Minimum time between (sets of) CAPI queries +capi_default_timeout = 10 auth_timeout = 30 # timeout for initial auth # Used by both class Auth and Session From bf33f88260f010e56ba971ccbef639e971488404 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 12:10:59 +0100 Subject: [PATCH 230/316] companion: Rename to capi_default_requests_timeout So it doesn't even need a comment. --- companion.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/companion.py b/companion.py index 8524b510..3fc4c817 100644 --- a/companion.py +++ b/companion.py @@ -49,7 +49,7 @@ else: # CAPIData = NewType('CAPIData', Dict) capi_query_cooldown = 60 # Minimum time between (sets of) CAPI queries -capi_default_timeout = 10 +capi_default_requests_timeout = 10 auth_timeout = 30 # timeout for initial auth # Used by both class Auth and Session @@ -734,7 +734,9 @@ class Session(object): """Worker thread that performs actual CAPI queries.""" logger.debug('CAPI worker thread starting') - def capi_single_query(capi_endpoint: str, timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 + def capi_single_query( # noqa: CCR001 + capi_endpoint: str, timeout: int = capi_default_requests_timeout + ) -> CAPIData: """ Perform a *single* CAPI endpoint query within the thread worker. @@ -797,7 +799,7 @@ class Session(object): return capi_data - def capi_station_queries(timeout: int = capi_default_timeout) -> CAPIData: # noqa: CCR001 + def capi_station_queries(timeout: int = capi_default_requests_timeout) -> CAPIData: # noqa: CCR001 """ Perform all 'station' queries for the caller. From af50a9f59d074a9a584560c680c24f93c9f426f6 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 12:44:46 +0100 Subject: [PATCH 231/316] companion: Make raw_data.__str__() a little less verbose NB: We can't use a generator here to make a python object of the data, to then use json.dumps() on because the raw_data is a *string* (decoded from what we received from the CAPI service), and thus it will get encoded as such, i.e. "raw_data": "{\"id\":322... when we want: "raw_data": {"id":322... We do not want to json.loads() that string only to then json.dumps() it because the whole point is that this is the **raw** data to help diagnose any issues with the CAPI service/data. Such a conversion and back could either throw an exception we don't want here (because we want the raw data) or possibly distort things from what was actually received. --- companion.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/companion.py b/companion.py index 3fc4c817..1d7f1b89 100644 --- a/companion.py +++ b/companion.py @@ -158,9 +158,9 @@ class CAPIDataRaw: def __str__(self): """Return a more readable string form of the data.""" capi_data_str = '{' - for e in self.raw_data.keys(): - capi_data_str += f'"{e}":\n{{\n\t"query_time": "{self.raw_data[e].query_time}",\n\t' \ - f'"raw_data": {self.raw_data[e].raw_data}\n}},\n\n' + for k, v in self.raw_data.items(): + capi_data_str += f'"{k}":\n{{\n\t"query_time": "{v.query_time}",\n\t' \ + f'"raw_data": {v.raw_data}\n}},\n\n' capi_data_str = capi_data_str.removesuffix(',\n\n') capi_data_str += '\n\n}' From 60379892eef125dfad70570474e1106771503244 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 13:07:00 +0100 Subject: [PATCH 232/316] companion: DRY try/except of CAPI queries --- companion.py | 60 +++++++++++++++++----------------------------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/companion.py b/companion.py index 1d7f1b89..957b7ee1 100644 --- a/companion.py +++ b/companion.py @@ -888,55 +888,33 @@ class Session(object): logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') capi_data: CAPIData - if query.endpoint == self._CAPI_PATH_STATION: - try: + try: + if query.endpoint == self._CAPI_PATH_STATION: capi_data = capi_station_queries() - except Exception as e: - self.capi_response_queue.put( - EDMCCAPIFailedRequest( - message=str(e.args), - exception=e, - query_time=query.query_time, - play_sound=query.play_sound, - auto_update=query.auto_update - ) - ) - else: - self.capi_response_queue.put( - EDMCCAPIResponse( - capi_data=capi_data, - query_time=query.query_time, - play_sound=query.play_sound, - auto_update=query.auto_update - ) - ) - - else: - try: capi_data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE) - except Exception as e: - self.capi_response_queue.put( - EDMCCAPIFailedRequest( - message=e.args, - exception=e, - query_time=query.query_time, - play_sound=query.play_sound, - auto_update=query.auto_update - ) + except Exception as e: + self.capi_response_queue.put( + EDMCCAPIFailedRequest( + message=str(e.args), + exception=e, + query_time=query.query_time, + play_sound=query.play_sound, + auto_update=query.auto_update ) + ) - else: - self.capi_response_queue.put( - EDMCCAPIResponse( - capi_data=capi_data, - query_time=query.query_time, - play_sound=query.play_sound, - auto_update=query.auto_update - ) + else: + self.capi_response_queue.put( + EDMCCAPIResponse( + capi_data=capi_data, + query_time=query.query_time, + play_sound=query.play_sound, + auto_update=query.auto_update ) + ) # If the query came from EDMC.(py|exe) there's no tk to send an # event too, so assume it will be polling there response queue. From 796bb1f8639c00c2ab9fa29375f0acb0b4842cb5 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 14:06:56 +0100 Subject: [PATCH 233/316] CAPI: Remove unuses Session.query/profile() * Session.profile() was unused (used to be called from stats.py, but that uses cached data now). * Session.query() was unused, other than by itself. Normal calls will currently be via companion.Session.station(). Future CAPI queries like `/fleetcarrier` might add their own companion.Session function. * And in doing so get --capi-pretend-down working again. * Small tweak to EDMarketConnector to not throw extra exception if there was a CAPI query exception. --- EDMarketConnector.py | 5 ++-- companion.py | 67 ++------------------------------------------ 2 files changed, 5 insertions(+), 67 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 53260e6f..77b07bfe 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1106,6 +1106,8 @@ class AppWindow(object): self.suit['text'] = f'{suitname} ({loadout_name})' self.suit_show_if_set() + # Update Odyssey Suit data + companion.session.suit_update(capi_response.capi_data) if capi_response.capi_data['commander'].get('credits') is not None: monitor.state['Credits'] = capi_response.capi_data['commander']['credits'] @@ -1174,9 +1176,6 @@ class AppWindow(object): if capi_response.play_sound and play_bad: hotkeymgr.play_bad() - # Update Odyssey Suit data - companion.session.suit_update(capi_response.capi_data) - logger.trace_if('capi.worker', 'Updating suit and cooldown...') self.update_suit_text() self.suit_show_if_set() diff --git a/companion.py b/companion.py index 957b7ee1..e61b39a7 100644 --- a/companion.py +++ b/companion.py @@ -747,6 +747,9 @@ class Session(object): capi_data: CAPIData try: logger.trace_if('capi.worker', 'Sending HTTP request...') + if conf_module.capi_pretend_down: + raise ServerConnectionError(f'Pretending CAPI down: {capi_endpoint}') + r = self.requests_session.get(self.server + capi_endpoint, timeout=timeout) # type: ignore logger.trace_if('capi.worker', '... got result...') r.raise_for_status() # Typically 403 "Forbidden" on token expiry @@ -933,70 +936,6 @@ class Session(object): ) ) - def query( - self, endpoint: str, query_time: int, - tk_response_event: Optional[str] = None, - play_sound: bool = False, auto_update: bool = False - ) -> None: - """ - Perform a query against the specified CAPI endpoint. - - :param endpoint: The CAPI endpoint to query. - :param tk_response_event: Name of tk event to generate when response queued. - :param query_time: When this query was initiated. - :param play_sound: Whether the app should play a sound on error. - :param auto_update: Whether this request was triggered automatically. - """ - logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') - if self.state == Session.STATE_INIT: - if self.login(): - self.query( - endpoint, query_time, tk_response_event=tk_response_event, play_sound=play_sound, - auto_update=auto_update - ) - return - - elif self.state == Session.STATE_AUTH: - logger.error('cannot make a query when unauthorized') - raise CredentialsError('cannot make a query when unauthorized') - - logger.trace_if('capi.query', 'Trying...') - if conf_module.capi_pretend_down: - raise ServerConnectionError(f'Pretending CAPI down: {endpoint}') - - self.capi_request_queue.put( - EDMCCAPIRequest( - endpoint=endpoint, - tk_response_event=tk_response_event, - query_time=query_time, - play_sound=play_sound, - auto_update=auto_update - ) - ) - - def profile( - self, - query_time: int = 0, - tk_response_event: Optional[str] = None, - play_sound: bool = False, auto_update: bool = False - ) -> None: - """ - Perform general CAPI /profile endpoint query. - - :param query_time: When this query was initiated. - :param tk_response_event: Name of tk event to generate when response queued. - :param play_sound: Whether the app should play a sound on error. - :param auto_update: Whether this request was triggered automatically. - """ - if query_time == 0: - query_time = int(time.time()) - - self.query( - self.FRONTIER_CAPI_PATH_PROFILE, query_time=query_time, - tk_response_event=tk_response_event, - play_sound=play_sound, auto_update=auto_update - ) - def station( self, query_time: int, tk_response_event: Optional[str] = None, play_sound: bool = False, auto_update: bool = False From 018ee49a0db7b3e4bd3e925538f92c5650739a6b Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 31 Aug 2021 15:03:41 +0100 Subject: [PATCH 234/316] Fix logic for 'Update' button visual state wrt CQC This was wrong since 291fbf2908e0f7419769a7727ee0a79cf58a9342 due to the check saying "only active if in CQC", when it needed to be "only active NOT in CQC, along with these other conditions". --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 77b07bfe..45389fa4 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1468,7 +1468,7 @@ class AppWindow(object): self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window self.button['state'] = self.theme_button['state'] = (monitor.cmdr and monitor.mode and - monitor.mode == 'CQC' and + monitor.mode != 'CQC' and not monitor.state['Captain'] and monitor.system and tk.NORMAL or tk.DISABLED) From 589895922899911a8a080399b7ce3b19f5f63822 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:03:50 +0000 Subject: [PATCH 235/316] build(deps-dev): bump pytest from 6.2.4 to 6.2.5 Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.4 to 6.2.5. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.4...6.2.5) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7e692601..e358714b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -31,7 +31,7 @@ grip==4.5.2 py2exe==0.10.4.0; sys_platform == 'win32' # Testing -pytest==6.2.4 +pytest==6.2.5 pytest-cov==2.12.1 # Pytest code coverage support coverage[toml]==5.5 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. From 0e4e31d733b42a04f3c3608f96a18c5847fe693c Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 2 Sep 2021 19:41:37 +0100 Subject: [PATCH 236/316] EDMarketConnector: Correct back to dashboard.start() Bad PyCharm refactor/rename in 81b5d015fdeecfcf2236a61c433d4c2dac929668 --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 45389fa4..911ebdd0 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1315,7 +1315,7 @@ class AppWindow(object): logger.info('Monitor: Disable WinSparkle automatic update checks') # Can't start dashboard monitoring - if not dashboard.start_frontier_auth(self.w, monitor.started): + if not dashboard.start(self.w, monitor.started): logger.info("Can't start Status monitoring") # Export loadout From 8180b34798f06e00871f331f1bf5162fa493f2cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Sep 2021 17:03:19 +0000 Subject: [PATCH 237/316] build(deps-dev): bump py2exe from 0.10.4.0 to 0.10.4.1 Bumps [py2exe](https://github.com/py2exe/py2exe) from 0.10.4.0 to 0.10.4.1. - [Release notes](https://github.com/py2exe/py2exe/releases) - [Changelog](https://github.com/py2exe/py2exe/blob/master/ChangeLog) - [Commits](https://github.com/py2exe/py2exe/compare/v0.10.4.0...v0.10.4.1) --- updated-dependencies: - dependency-name: py2exe dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e358714b..14bc577f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,7 +28,7 @@ grip==4.5.2 # Packaging # We only need py2exe on windows. -py2exe==0.10.4.0; sys_platform == 'win32' +py2exe==0.10.4.1; sys_platform == 'win32' # Testing pytest==6.2.5 From cbcf5f6d44d143faec2af8f8a4e4559de3dd05ad Mon Sep 17 00:00:00 2001 From: Forest <foresto@users.noreply.github.com> Date: Fri, 17 Sep 2021 17:26:22 -0700 Subject: [PATCH 238/316] Log git's absence at the INFO level, not ERROR The ERROR log level is needlessly high for complaining about git not being installed, especially when we're running from an extracted source archive rather than a repo. Let's keep things calm and avoid polluting desktop session logs. The INFO log level is plenty. --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index d1cfd403..b1437047 100644 --- a/config.py +++ b/config.py @@ -117,7 +117,7 @@ def git_shorthash_from_head() -> str: out, err = git_cmd.communicate() except Exception as e: - logger.error(f"Couldn't run git command for short hash: {e!r}") + logger.info(f"Couldn't run git command for short hash: {e!r}") else: shorthash = out.decode().rstrip('\n') From 8c0ea8905888a8d0e4407de03b466c644f00175c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Sep 2021 17:03:29 +0000 Subject: [PATCH 239/316] build(deps-dev): bump types-requests from 2.25.6 to 2.25.8 Bumps [types-requests](https://github.com/python/typeshed) from 2.25.6 to 2.25.8. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e358714b..28e9dd68 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.1 mypy==0.910 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.25.6 +types-requests==2.25.8 # Code formatting tools autopep8==1.5.7 From e6e1cbd2215fafb3c6bb274c935df4e8ec9c7153 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 24 Sep 2021 10:34:42 +0100 Subject: [PATCH 240/316] CL args: Group them in-code --- EDMarketConnector.py | 48 +++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 911ebdd0..0797f814 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -70,6 +70,28 @@ if __name__ == '__main__': # noqa: C901 "such as EDSM, Inara.cz and EDDB." ) + ########################################################################### + # Permanent config changes + ########################################################################### + parser.add_argument( + '--reset-ui', + help='reset UI theme and transparency to defaults', + action='store_true' + ) + ########################################################################### + + ########################################################################### + # User 'utility' args + ########################################################################### + parser.add_argument('--suppress-dupe-process-popup', + help='Suppress the popup from when the application detects another instance already running', + action='store_true' + ) + ########################################################################### + + ########################################################################### + # Adjust logging + ########################################################################### parser.add_argument( '--trace', help='Set the Debug logging loglevel to TRACE', @@ -89,28 +111,21 @@ if __name__ == '__main__': # noqa: C901 ) parser.add_argument( - '--reset-ui', - help='reset UI theme and transparency to defaults', - action='store_true' + '--debug-sender', + help='Mark the selected sender as in debug mode. This generally results in data being written to disk', + action='append', ) + ########################################################################### + ########################################################################### + # Frontier Auth + ########################################################################### parser.add_argument( '--forget-frontier-auth', help='resets all authentication tokens', action='store_true' ) - parser.add_argument('--suppress-dupe-process-popup', - help='Suppress the popup from when the application detects another instance already running', - action='store_true' - ) - - parser.add_argument( - '--debug-sender', - help='Mark the selected sender as in debug mode. This generally results in data being written to disk', - action='append', - ) - auth_options = parser.add_mutually_exclusive_group(required=False) auth_options.add_argument('--force-localserver-for-auth', help='Force EDMC to use a localhost webserver for Frontier Auth callback', @@ -126,12 +141,17 @@ if __name__ == '__main__': # noqa: C901 help='Callback from Frontier Auth', nargs='*' ) + ########################################################################### + ########################################################################### + # Developer 'utility' args + ########################################################################### parser.add_argument( '--capi-pretend-down', help='Force to raise ServerError on any CAPI query', action='store_true' ) + ########################################################################### args = parser.parse_args() From f9875b5b9a597db3e64e868c9abcdaff783009ea Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 24 Sep 2021 10:59:15 +0100 Subject: [PATCH 241/316] EDDN: Make use of new CL arg to use custom upload URL This also entailed slightly reworking the way the EDDN code uses this URL. It was very generalised, so as to allow for the debug "just send and log locally" code, but as the only URL is the 'upload' one much of that seemed un-necessary. So that code has been simplified. --- EDMarketConnector.py | 8 ++++++++ config.py | 14 ++++++++++++++ plugins/eddn.py | 19 ++++++++++--------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 0797f814..094a6015 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -151,6 +151,11 @@ if __name__ == '__main__': # noqa: C901 help='Force to raise ServerError on any CAPI query', action='store_true' ) + + parser.add_argument( + '--eddn-url', + help='Specify an alternate EDDN upload URL', + ) ########################################################################### args = parser.parse_args() @@ -176,6 +181,9 @@ if __name__ == '__main__': # noqa: C901 if args.force_localserver_for_auth: config.set_auth_force_localserver() + if args.eddn_url: + config.set_eddn_url(args.eddn_url) + if args.force_edmc_protocol: if sys.platform == 'win32': config.set_auth_force_edmc_protocol() diff --git a/config.py b/config.py index b1437047..7a51547a 100644 --- a/config.py +++ b/config.py @@ -207,6 +207,7 @@ class AbstractConfig(abc.ABC): __in_shutdown = False # Is the application currently shutting down ? __auth_force_localserver = False # Should we use localhost for auth callback ? __auth_force_edmc_protocol = False # Should we force edmc:// protocol ? + __eddn_url = None # Non-default EDDN URL def __init__(self) -> None: self.home_path = pathlib.Path.home() @@ -250,6 +251,19 @@ class AbstractConfig(abc.ABC): """ return self.__auth_force_edmc_protocol + def set_eddn_url(self, eddn_url: str): + """Set the specified eddn URL.""" + self.__eddn_url = eddn_url + + @property + def eddn_url(self) -> Optional[str]: + """ + Provide the custom EDDN URL. + + :return: str - Custom EDDN URL to use. + """ + return self.__eddn_url + @property def app_dir(self) -> str: """Return a string version of app_dir.""" diff --git a/plugins/eddn.py b/plugins/eddn.py index 36a4cb94..339baec0 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -88,14 +88,9 @@ HORIZ_SKU = 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' class EDDN: """EDDN Data export.""" - DEBUG = 'eddn' in debug_senders - SERVER = 'https://eddn.edcd.io:4430' - if DEBUG: - SERVER = f'http://{edmc_data.DEBUG_WEBSERVER_HOST}:{edmc_data.DEBUG_WEBSERVER_PORT}' - - UPLOAD = f'{SERVER}/upload/' - if DEBUG: - UPLOAD = f'{SERVER}/eddn' + DEFAULT_URL = 'https://eddn.edcd.io:4430/upload' + if 'eddn' in debug_senders: + DEFAULT_URL = f'http://{edmc_data.DEBUG_WEBSERVER_HOST}:{edmc_data.DEBUG_WEBSERVER_PORT}/eddn' REPLAYPERIOD = 400 # Roughly two messages per second, accounting for send delays [ms] REPLAYFLUSH = 20 # Update log on disk roughly every 10 seconds @@ -109,6 +104,12 @@ class EDDN: self.replayfile: Optional[TextIO] = None # For delayed messages self.replaylog: List[str] = [] + if config.eddn_url is not None: + self.eddn_url = config.eddn_url + + else: + self.eddn_url = self.DEFAULT_URL + def load_journal_replay(self) -> bool: """ Load cached journal entries from disk. @@ -187,7 +188,7 @@ class EDDN: ('message', msg['message']), ]) - r = self.session.post(self.UPLOAD, data=json.dumps(to_send), timeout=self.TIMEOUT) + r = self.session.post(self.eddn_url, data=json.dumps(to_send), timeout=self.TIMEOUT) if r.status_code != requests.codes.ok: # Check if EDDN is still objecting to an empty commodities list From 5b68aad1846a9c23347e4a439eafe1ae84c90b0e Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 24 Sep 2021 16:39:30 +0200 Subject: [PATCH 242/316] Remove protocol.protocolhandler singleton auto-init In general, doing things like this on import is bad, but this was changed specifically to remove a bug that causes --force-localserver-auth to do nothing. That is caused because while we were careful not to import protocol until after we were done doing arg things, we did not check to make sure something else didn't. Companion imports protocol, which thus always instantiates WindowsProtocolHandler before we can modify some config state to indicate that we want LinuxProtocolHandler. --- EDMarketConnector.py | 9 ++++++--- companion.py | 9 +++++---- protocol.py | 47 ++++++++++++++++++++++++-------------------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 094a6015..ec7db931 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -377,7 +377,7 @@ from edmc_data import ship_name_map from hotkey import hotkeymgr from l10n import Translations from monitor import monitor -from protocol import protocolhandler +import protocol from theme import theme from ttkHyperlinkLabel import HyperlinkLabel @@ -689,7 +689,7 @@ class AppWindow(object): self.w.bind_all('<<Quit>>', self.onexit) # Updater # Start a protocol handler to handle cAPI registration. Requires main loop to be running. - self.w.after_idle(lambda: protocolhandler.start(self.w)) + self.w.after_idle(lambda: protocol.protocolhandler.start(self.w)) # Load updater after UI creation (for WinSparkle) import update @@ -1698,7 +1698,7 @@ class AppWindow(object): # Frontier auth/CAPI handling logger.info('Closing protocol handler...') - protocolhandler.close() + protocol.protocolhandler.close() logger.info('Closing Frontier CAPI sessions...') companion.session.close() @@ -1900,6 +1900,9 @@ sys.path: {sys.path}''' logger.info("Dropping all fdev tokens as --forget-frontier-auth was passed") companion.Auth.invalidate(None) + # Create protocol handler + protocol.protocolhandler = protocol.get_handler_impl()() + # TODO: unittests in place of these # logger.debug('Test from __main__') # test_logging() diff --git a/companion.py b/companion.py index e61b39a7..16839925 100644 --- a/companion.py +++ b/companion.py @@ -33,7 +33,8 @@ from config import appname, appversion, config from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor -from protocol import protocolhandler +# from protocol import protocolhandler +import protocol logger = get_main_logger() @@ -375,7 +376,7 @@ class Auth(object): f'&code_challenge={challenge}' f'&code_challenge_method=S256' f'&state={self.state}' - f'&redirect_uri={protocolhandler.redirect}' + f'&redirect_uri={protocol.protocolhandler.redirect}' ) return None @@ -412,7 +413,7 @@ class Auth(object): 'client_id': self.CLIENT_ID, 'code_verifier': self.verifier, 'code': data['code'][0], - 'redirect_uri': protocolhandler.redirect, + 'redirect_uri': protocol.protocolhandler.redirect, } # import http.client as http_client @@ -698,7 +699,7 @@ class Session(object): try: logger.debug('Trying authorize with payload from handler') - self.start_frontier_auth(self.auth.authorize(protocolhandler.lastpayload)) # type: ignore + self.start_frontier_auth(self.auth.authorize(protocol.protocolhandler.lastpayload)) # type: ignore self.auth = None except Exception: diff --git a/protocol.py b/protocol.py index ce5ea042..6db5d5be 100644 --- a/protocol.py +++ b/protocol.py @@ -1,12 +1,13 @@ """protocol handler for cAPI authorisation.""" +# spell-checker: words ntdll GURL alloc wfile instantiatable pyright import os import sys import threading import urllib.error import urllib.parse import urllib.request -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Type from config import config from constants import appname, protocolhandler_redirect @@ -115,10 +116,10 @@ if sys.platform == 'darwin' and getattr(sys, 'frozen', False): # noqa: C901 # i 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 + sys.platform == 'win32' + and getattr(sys, 'frozen', False) + and not is_wine + and not config.auth_force_localserver )): # spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL from ctypes import windll # type: ignore @@ -415,20 +416,24 @@ else: # Linux / Run from source pass -# singleton +def get_handler_impl() -> Type[GenericProtocolHandler]: + """ + Get the appropriate GenericProtocolHandler for the current system and config. + + :return: An instantiatable GenericProtocolHandler + """ + if sys.platform == 'darwin' and getattr(sys, 'frozen', False): + return DarwinProtocolHandler # pyright: reportUnboundVariable=false + + elif ( + (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 + + else: + return LinuxProtocolHandler + + +# *late init* singleton protocolhandler: GenericProtocolHandler - -if sys.platform == 'darwin' and getattr(sys, 'frozen', False): - protocolhandler = DarwinProtocolHandler() # pyright: reportUnboundVariable=false - -elif ( - sys.platform == 'win32' - and config.auth_force_edmc_protocol or ( - getattr(sys, 'frozen', False) - and not is_wine - and not config.auth_force_localserver - ) - ): - protocolhandler = WindowsProtocolHandler() -else: - protocolhandler = LinuxProtocolHandler() From 30b2d481c4f0166e114f175196319d2cf6950b6a Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 24 Sep 2021 17:01:57 +0200 Subject: [PATCH 243/316] hack to fix --force-localserver-auth --- EDMarketConnector.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 094a6015..28160c87 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -56,7 +56,6 @@ import killswitch from config import appversion, appversion_nobuild, config, copyright # isort: on -from companion import CAPIData, index_possibly_sparse_list from EDMCLogging import edmclogger, logger, logging from journal_lock import JournalLock, JournalLockResult @@ -881,7 +880,7 @@ class AppWindow(object): self.cooldown() - def export_market_data(self, data: CAPIData) -> bool: # noqa: CCR001 + def export_market_data(self, data: 'CAPIData') -> bool: # noqa: CCR001 """ Export CAPI market data. @@ -1900,6 +1899,10 @@ sys.path: {sys.path}''' logger.info("Dropping all fdev tokens as --forget-frontier-auth was passed") companion.Auth.invalidate(None) + # HACK: n/a | 2021-11-24: --force-localserver-auth does not work if companion is imported early -cont. + # HACK: n/a | 2021-11-24: as we modify config before this is used. + from companion import CAPIData, index_possibly_sparse_list + # TODO: unittests in place of these # logger.debug('Test from __main__') # test_logging() From c0351fec9b62bcb4dd6a5ec3b10ad9185ad7d8dd Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 24 Sep 2021 17:06:51 +0200 Subject: [PATCH 244/316] missed an import --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 28160c87..675806f6 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -365,7 +365,6 @@ import tkinter.messagebox from tkinter import ttk import commodity -import companion import plug import prefs import stats @@ -1901,6 +1900,7 @@ sys.path: {sys.path}''' # HACK: n/a | 2021-11-24: --force-localserver-auth does not work if companion is imported early -cont. # HACK: n/a | 2021-11-24: as we modify config before this is used. + import companion from companion import CAPIData, index_possibly_sparse_list # TODO: unittests in place of these From f6e4bcfcba9a5c3a5be62c5cac24ca68f6620164 Mon Sep 17 00:00:00 2001 From: A_D <aunderscored@gmail.com> Date: Fri, 24 Sep 2021 17:13:00 +0200 Subject: [PATCH 245/316] importing things before you use them is helpful. --- EDMarketConnector.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 675806f6..21edf222 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1893,16 +1893,16 @@ sys.path: {sys.path}''' else: log_locale('After switching to UTF-8 encoding (same language)') - # Do this after locale silliness, just in case - if args.forget_frontier_auth: - logger.info("Dropping all fdev tokens as --forget-frontier-auth was passed") - companion.Auth.invalidate(None) - # HACK: n/a | 2021-11-24: --force-localserver-auth does not work if companion is imported early -cont. # HACK: n/a | 2021-11-24: as we modify config before this is used. import companion from companion import CAPIData, index_possibly_sparse_list + # Do this after locale silliness, just in case + if args.forget_frontier_auth: + logger.info("Dropping all fdev tokens as --forget-frontier-auth was passed") + companion.Auth.invalidate(None) + # TODO: unittests in place of these # logger.debug('Test from __main__') # test_logging() From 0e42358394810510c3e47ecf9f1118cd9d60553a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Sep 2021 17:03:27 +0000 Subject: [PATCH 246/316] build(deps-dev): bump types-requests from 2.25.8 to 2.25.9 Bumps [types-requests](https://github.com/python/typeshed) from 2.25.8 to 2.25.9. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 885b7064..d7d7f009 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.1 mypy==0.910 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.25.8 +types-requests==2.25.9 # Code formatting tools autopep8==1.5.7 From e11b357502ff9165ec160c62dba84fdef0243874 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 1 Oct 2021 08:34:50 +0100 Subject: [PATCH 247/316] EDDN: DEFAULT URL requires `/` on the end The EDDN server config does NOT redirect from /upload to /upload/ --- plugins/eddn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 339baec0..44b8c02c 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -88,7 +88,7 @@ HORIZ_SKU = 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' class EDDN: """EDDN Data export.""" - DEFAULT_URL = 'https://eddn.edcd.io:4430/upload' + DEFAULT_URL = 'https://eddn.edcd.io:4430/upload/' if 'eddn' in debug_senders: DEFAULT_URL = f'http://{edmc_data.DEBUG_WEBSERVER_HOST}:{edmc_data.DEBUG_WEBSERVER_PORT}/eddn' From de44b0d9d6fea4f4755871f9d0346c77ebbe6ca3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 17:03:45 +0000 Subject: [PATCH 248/316] build(deps): bump watchdog from 2.1.5 to 2.1.6 Bumps [watchdog](https://github.com/gorakhargosh/watchdog) from 2.1.5 to 2.1.6. - [Release notes](https://github.com/gorakhargosh/watchdog/releases) - [Changelog](https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst) - [Commits](https://github.com/gorakhargosh/watchdog/compare/v2.1.5...v2.1.6) --- updated-dependencies: - dependency-name: watchdog dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 706d86d4..82a49865 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ certifi==2021.5.30 requests==2.26.0 -watchdog==2.1.5 +watchdog==2.1.6 # Commented out because this doesn't package well with py2exe # infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep From 69e8a81e6c4494e933e104ef114b3910a30fcc55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 17:03:47 +0000 Subject: [PATCH 249/316] build(deps-dev): bump pytest-cov from 2.12.1 to 3.0.0 Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.12.1 to 3.0.0. - [Release notes](https://github.com/pytest-dev/pytest-cov/releases) - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.12.1...v3.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 885b7064..ea8ac908 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -32,7 +32,7 @@ py2exe==0.10.4.1; sys_platform == 'win32' # Testing pytest==6.2.5 -pytest-cov==2.12.1 # Pytest code coverage support +pytest-cov==3.0.0 # Pytest code coverage support coverage[toml]==5.5 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. pywin32==301; sys_platform == 'win32' From 3f5c77f7c3d50b1b5186aed73658fb4ced66aa28 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 10 Oct 2021 12:54:17 +0000 Subject: [PATCH 250/316] build(deps-dev): bump coverage[toml] from 5.5 to 6.0.1 Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 5.5 to 6.0.1. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/5.5...6.0.1) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cba99dd6..a1aebfb7 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -33,7 +33,7 @@ py2exe==0.10.4.1; sys_platform == 'win32' # Testing pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support -coverage[toml]==5.5 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==6.0.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. pywin32==301; sys_platform == 'win32' From c4ba8acb17a99c8e4dbde062bab3c8c0ef0bee34 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 24 Sep 2021 16:19:28 +0100 Subject: [PATCH 251/316] EDDN: Add support for EDDN FSSDiscoveryScan schema from that event * As we're now not sending *all* journal events on the journal schema this refactors only the actual sending in export_journal_entry(), with the other functionality in export_journal_generic(), so that the new export_journal_fssdiscoveryscan() can use export_journal_entry() as well. In the future EDDN will move to a schema per event type, so we'll lose _generic(). Factoring out some things like augmentations into their own functions will come next. --- plugins/eddn.py | 119 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 28 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 44b8c02c..d9575135 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -599,21 +599,16 @@ Msg:\n{msg}''' # this.shipyard = (horizons, shipyard) - def export_journal_entry(self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]) -> None: + def export_journal_entry(self, cmdr, entry, msg): """ Update EDDN with an event from the journal. Additionally if additional lines are cached, it may send those as well. - :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 + :param cmdr: Commander name as passed in through `journal_entry()`. + :param entry: The full journal event dictionary (due to checks in this function). + :param msg: The EDDN message body to be sent. """ - msg = { - '$schemaRef': f'https://eddn.edcd.io/schemas/journal/1{"/test" if is_beta else ""}', - 'message': entry - } - if self.replayfile or self.load_journal_replay(): # Store the entry self.replaylog.append(json.dumps([cmdr, msg])) @@ -633,6 +628,62 @@ Msg:\n{msg}''' self.send(cmdr, msg) self.parent.children['status']['text'] = '' + def export_journal_generic(self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]) -> None: + """ + Send an EDDN event on the journal 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 + """ + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/journal/1{"/test" if is_beta else ""}', + 'message': entry + } + this.eddn.export_journal_entry(cmdr, entry, msg) + + def export_journal_fssdiscoveryscan( + self, cmdr: str, system: str, is_beta: bool, entry: Mapping[str, Any] + ) -> Optional[str]: + """ + Send an FSSDiscoveryScan 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 + """ + ####################################################################### + # Elisions + entry = filter_localised(entry) + entry.pop('Progress') + ####################################################################### + + ####################################################################### + # Augmentations + ####################################################################### + entry['SystemName'] = system + + if this.systemaddress is None: + logger.warning("this.systemaddress is None, can't add SystemAddress") + return "this.systemaddress is None, can't add SystemAddress" + + entry['SystemAddress'] = this.systemaddress + + if not this.coordinates: + logger.warning("this.coordinates is None, can't add StarPos") + return "this.coordinates is None, can't add StarPos" + + entry['StarPos'] = list(this.coordinates) + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/fssdiscoveryscan/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -773,6 +824,31 @@ def plugin_stop() -> None: logger.debug('Done.') +# Recursively filter '*_Localised' keys from dict +def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: + """ + 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() + for k, v in d.items(): + if k.endswith('_Localised'): + pass + + elif hasattr(v, 'items'): # dict -> recurse + filtered[k] = filter_localised(v) + + elif isinstance(v, list): # list of dicts -> recurse + filtered[k] = [filter_localised(x) if hasattr(x, 'items') else x for x in v] + + else: + filtered[k] = v + + return filtered + + def journal_entry( # noqa: C901, CCR001 cmdr: str, is_beta: bool, @@ -803,24 +879,6 @@ def journal_entry( # noqa: C901, CCR001 entry = new_data - # Recursively filter '*_Localised' keys from dict - def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: - filtered: OrderedDictT[str, Any] = OrderedDict() - for k, v in d.items(): - if k.endswith('_Localised'): - pass - - elif hasattr(v, 'items'): # dict -> recurse - filtered[k] = filter_localised(v) - - elif isinstance(v, list): # list of dicts -> recurse - filtered[k] = [filter_localised(x) if hasattr(x, 'items') else x for x in v] - - else: - filtered[k] = v - - return filtered - this.on_foot = state['OnFoot'] # Note if we're under Odyssey @@ -849,6 +907,11 @@ def journal_entry( # noqa: C901, CCR001 elif entry['event'] in ('LeaveBody', 'SupercruiseEntry'): this.planet = None + if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: + # Events with their own EDDN schema + if entry['event'].lower() == 'fssdiscoveryscan': + this.eddn.export_journal_fssdiscoveryscan(cmdr, system, is_beta, entry) + # Send interesting events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and (entry['event'] in ('Location', 'FSDJump', 'Docked', 'Scan', 'SAASignalsFound', 'CarrierJump')) and @@ -914,7 +977,7 @@ def journal_entry( # noqa: C901, CCR001 entry['SystemAddress'] = this.systemaddress try: - this.eddn.export_journal_entry(cmdr, is_beta, filter_localised(entry)) + this.eddn.export_journal_generic(cmdr, is_beta, filter_localised(entry)) except requests.exceptions.RequestException as e: logger.debug('Failed in export_journal_entry', exc_info=e) From 1dda61907b72ae05aefd47814533b8c12e0ecde4 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 24 Sep 2021 19:09:39 +0100 Subject: [PATCH 252/316] EDDN: TODO on refactoring & return result of fssdiscoveryscan export --- plugins/eddn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index d9575135..2cd5f2f0 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -661,6 +661,7 @@ Msg:\n{msg}''' ####################################################################### # Augmentations ####################################################################### + # TODO: See if any of this can be factored out into helper functions. entry['SystemName'] = system if this.systemaddress is None: @@ -910,7 +911,7 @@ def journal_entry( # noqa: C901, CCR001 if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: # Events with their own EDDN schema if entry['event'].lower() == 'fssdiscoveryscan': - this.eddn.export_journal_fssdiscoveryscan(cmdr, system, is_beta, entry) + return this.eddn.export_journal_fssdiscoveryscan(cmdr, system, is_beta, entry) # Send interesting events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and From 413f9049f56cb9c51090d96be9ea709bfc19bb7b Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 26 Sep 2021 15:06:00 +0100 Subject: [PATCH 253/316] EDDN: Add support for NavBeaconScan on its own schema * Refactors the augmentation code for use by both fssdiscoveryscan and navbeaconscan. --- plugins/eddn.py | 90 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 2cd5f2f0..f4d72673 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -12,7 +12,7 @@ from os.path import join from platform import system from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Mapping, MutableMapping, Optional from typing import OrderedDict as OrderedDictT -from typing import TextIO, Tuple +from typing import TextIO, Tuple, Union import requests @@ -642,6 +642,36 @@ Msg:\n{msg}''' } this.eddn.export_journal_entry(cmdr, entry, msg) + def entry_augment_system_data(self, entry: Dict[str, Any], system_name: str) -> Union[str, Mapping[str, Any]]: + """ + Augment a journal entry with necessary system data. + + :param entry: The journal entry to be augmented. + :param system_name: Name of current star system. + :param systemname_field_name: Name of journal key for system name. + :return: The augmented version of entry. + """ + # If 'SystemName' is there, it's directly from a journal event. + # If that's not there *and* 'StarSystem' isn't either, then we add the latter. + if entry.get('SystemName') is None and entry.get('StarSystem') is None: + entry['StarSystem'] = system_name + + if entry.get('SystemAddress') is None: + if this.systemaddress is None: + logger.warning("this.systemaddress is None, can't add SystemAddress") + return "this.systemaddress is None, can't add SystemAddress" + + entry['SystemAddress'] = this.systemaddress + + if entry.get('StarPos') is None: + if not this.coordinates: + logger.warning("this.coordinates is None, can't add StarPos") + return "this.coordinates is None, can't add StarPos" + + entry['StarPos'] = list(this.coordinates) + + return entry + def export_journal_fssdiscoveryscan( self, cmdr: str, system: str, is_beta: bool, entry: Mapping[str, Any] ) -> Optional[str]: @@ -661,20 +691,11 @@ Msg:\n{msg}''' ####################################################################### # Augmentations ####################################################################### - # TODO: See if any of this can be factored out into helper functions. - entry['SystemName'] = system + ret = this.eddn.entry_augment_system_data(entry, system) + if isinstance(ret, str): + return ret - if this.systemaddress is None: - logger.warning("this.systemaddress is None, can't add SystemAddress") - return "this.systemaddress is None, can't add SystemAddress" - - entry['SystemAddress'] = this.systemaddress - - if not this.coordinates: - logger.warning("this.coordinates is None, can't add StarPos") - return "this.coordinates is None, can't add StarPos" - - entry['StarPos'] = list(this.coordinates) + entry = ret ####################################################################### msg = { @@ -685,6 +706,40 @@ Msg:\n{msg}''' this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_journal_navbeaconscan( + self, cmdr: str, system: str, is_beta: bool, entry: Mapping[str, Any] + ) -> Optional[str]: + """ + Send an NavBeaconScan 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 + """ + # { "timestamp":"2021-09-24T13:57:24Z", "event":"NavBeaconScan", "SystemAddress":670417626481, "NumBodies":18 } + ####################################################################### + # Elisions + entry = filter_localised(entry) + ####################################################################### + + ####################################################################### + # Augmentations + ####################################################################### + ret = this.eddn.entry_augment_system_data(entry, system) + if isinstance(ret, str): + return ret + + entry = ret + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/navbeaconscan/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -908,12 +963,15 @@ def journal_entry( # noqa: C901, CCR001 elif entry['event'] in ('LeaveBody', 'SupercruiseEntry'): this.planet = None + # Events with their own EDDN schema if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: - # Events with their own EDDN schema if entry['event'].lower() == 'fssdiscoveryscan': return this.eddn.export_journal_fssdiscoveryscan(cmdr, system, is_beta, entry) - # Send interesting events to EDDN, but not when on a crew + if entry['event'].lower() == 'navbeaconscan': + return this.eddn.export_journal_navbeaconscan(cmdr, system, is_beta, entry) + + # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and (entry['event'] in ('Location', 'FSDJump', 'Docked', 'Scan', 'SAASignalsFound', 'CarrierJump')) and ('StarPos' in entry or this.coordinates)): From 9c611d73d5d1a928092eddd9c1aa2a871b7e5441 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 26 Sep 2021 15:17:37 +0100 Subject: [PATCH 254/316] EDDN: TODO for dropping messages if we get a clear HTTP error i.e. message not matching schema --- plugins/eddn.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index f4d72673..64ea374d 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -266,6 +266,9 @@ Msg:\n{msg}''' if not len(self.replaylog) % self.REPLAYFLUSH: self.flush() + # TODO: Something here needs to handle, e.g. HTTP 400, and take the message + # in question out of replaylog, else we'll keep retrying a bad message + # forever. except requests.exceptions.HTTPError as e: status['text'] = self.http_error_to_log(e) From f97cf0897a34476801de4c9d9a996f6a4b472edf Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 26 Sep 2021 15:59:52 +0100 Subject: [PATCH 255/316] EDDN: Add CodexEntry on its own schema This requires a tweak to `entry_augment_system_data()` as we now also have 'System' as a possible key for a system name. --- plugins/eddn.py | 69 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 64ea374d..cd4ccf00 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -654,9 +654,9 @@ Msg:\n{msg}''' :param systemname_field_name: Name of journal key for system name. :return: The augmented version of entry. """ - # If 'SystemName' is there, it's directly from a journal event. - # If that's not there *and* 'StarSystem' isn't either, then we add the latter. - if entry.get('SystemName') is None and entry.get('StarSystem') is None: + # If 'SystemName' or 'System' is there, it's directly from a journal event. + # If they're not there *and* 'StarSystem' isn't either, then we add the latter. + if entry.get('SystemName') is None and entry.get('System') is None and entry.get('StarSystem') is None: entry['StarSystem'] = system_name if entry.get('SystemAddress') is None: @@ -710,12 +710,13 @@ Msg:\n{msg}''' return None def export_journal_navbeaconscan( - self, cmdr: str, system: str, is_beta: bool, entry: Mapping[str, Any] + self, cmdr: str, system_name: str, is_beta: bool, entry: Mapping[str, Any] ) -> Optional[str]: """ Send an NavBeaconScan to EDDN on the correct schema. :param cmdr: the commander under which this upload is made + :param system_name: Name of the current system. :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -728,7 +729,7 @@ Msg:\n{msg}''' ####################################################################### # Augmentations ####################################################################### - ret = this.eddn.entry_augment_system_data(entry, system) + ret = this.eddn.entry_augment_system_data(entry, system_name) if isinstance(ret, str): return ret @@ -743,6 +744,61 @@ Msg:\n{msg}''' this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_journal_codexentry( + self, cmdr: str, is_beta: bool, entry: Mapping[str, Any] + ) -> Optional[str]: + """ + Send a CodexEntry 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 + """ + # { + # "timestamp":"2021-09-26T12:29:39Z", + # "event":"CodexEntry", + # "EntryID":1400414, + # "Name":"$Codex_Ent_Gas_Vents_SilicateVapourGeysers_Name;", + # "Name_Localised":"Silicate Vapour Gas Vent", + # "SubCategory":"$Codex_SubCategory_Geology_and_Anomalies;", + # "SubCategory_Localised":"Geology and anomalies", + # "Category":"$Codex_Category_Biology;", + # "Category_Localised":"Biological and Geological", + # "Region":"$Codex_RegionName_18;", + # "Region_Localised":"Inner Orion Spur", + # "System":"Bestia", + # "SystemAddress":147916327267, + # "Latitude":23.197777, "Longitude":51.803349, + # "IsNewEntry":true, + # "VoucherAmount":50000 + # } + ####################################################################### + # Elisions + entry = filter_localised(entry) + # Keys specific to this event + for k in ('IsNewEntry', 'NewTraitsDiscovered'): + if entry.get(k) is not None: + del entry[k] + ####################################################################### + + ####################################################################### + # Augmentations + ####################################################################### + ret = this.eddn.entry_augment_system_data(entry, entry['System']) + if isinstance(ret, str): + return ret + + entry = ret + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/codexentry/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -974,6 +1030,9 @@ def journal_entry( # noqa: C901, CCR001 if entry['event'].lower() == 'navbeaconscan': return this.eddn.export_journal_navbeaconscan(cmdr, system, is_beta, entry) + if entry['event'].lower() == 'codexentry': + return this.eddn.export_journal_codexentry(cmdr, is_beta, entry) + # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and (entry['event'] in ('Location', 'FSDJump', 'Docked', 'Scan', 'SAASignalsFound', 'CarrierJump')) and From 7081a5a3abe9d7829280f0625561a842f3ca9671 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 26 Sep 2021 16:14:09 +0100 Subject: [PATCH 256/316] EDDN: Send ScanBaryCentre on correct schema --- plugins/eddn.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index cd4ccf00..b506c709 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -799,6 +799,53 @@ Msg:\n{msg}''' this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_journal_scanbarycentre( + self, cmdr: str, is_beta: bool, entry: Mapping[str, Any] + ) -> Optional[str]: + """ + Send a ScanBaryCentre 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 + """ + # { + # "timestamp":"2021-09-26T11:55:03Z", + # "event":"ScanBaryCentre", + # "StarSystem":"Khruvani", + # "SystemAddress":13864557159793, + # "BodyID":21, + # "SemiMajorAxis":863683605194.091797, + # "Eccentricity":0.001446, + # "OrbitalInclination":-0.230714, + # "Periapsis":214.828581, + # "OrbitalPeriod":658474677.801132, + # "AscendingNode":21.188568, + # "MeanAnomaly":208.765388 + # } + ####################################################################### + # Elisions + entry = filter_localised(entry) + ####################################################################### + + ####################################################################### + # Augmentations + ####################################################################### + ret = this.eddn.entry_augment_system_data(entry, entry['StarSystem']) + if isinstance(ret, str): + return ret + + entry = ret + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/scanbarycentre/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -1033,6 +1080,9 @@ def journal_entry( # noqa: C901, CCR001 if entry['event'].lower() == 'codexentry': return this.eddn.export_journal_codexentry(cmdr, is_beta, entry) + if entry['event'].lower() == 'scanbarycentre': + return this.eddn.export_journal_scanbarycentre(cmdr, is_beta, entry) + # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and (entry['event'] in ('Location', 'FSDJump', 'Docked', 'Scan', 'SAASignalsFound', 'CarrierJump')) and From 0ffbb4dac96f96d73c5e65f0a496e3289bcd422e Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 26 Sep 2021 16:37:01 +0100 Subject: [PATCH 257/316] EDDN: Send 'NavRoute' data on appropriate schema Yes, there's some lovely mis-match between Mapping and Dict type to work around. Mapping complains about lack of __delitem__, so saying it's a Dict instead and ensuring that matches at the call site. --- plugins/eddn.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index b506c709..46c011a3 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -846,6 +846,62 @@ Msg:\n{msg}''' this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_journal_navroute( + self, cmdr: str, is_beta: bool, entry: Dict[str, Any] + ) -> Optional[str]: + """ + Send a NavRoute 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 + """ + # { + # "timestamp":"2021-09-24T14:33:15Z", + # "event":"NavRoute", + # "Route":[ + # { + # "StarSystem":"Ross 332", + # "SystemAddress":3657198211778, + # "StarPos":[-43.62500,-23.15625,-74.12500], + # "StarClass":"K" + # }, + # { + # "StarSystem":"BD+44 1040", + # "SystemAddress":2832564490946, + # "StarPos":[-31.56250,1.84375,-92.37500], + # "StarClass":"K" + # }, + # { + # "StarSystem":"Aonga", + # "SystemAddress":6680855188162, + # "StarPos":[-34.46875,9.53125,-90.87500], + # "StarClass":"M" + # } + # ] + # } + ####################################################################### + # Elisions + ####################################################################### + # This will reject with the Odyssey flag present + if entry.get('odyssey') is not None: + del entry['odyssey'] + ####################################################################### + + ####################################################################### + # Augmentations + ####################################################################### + # None + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/navroute/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -1083,6 +1139,9 @@ def journal_entry( # noqa: C901, CCR001 if entry['event'].lower() == 'scanbarycentre': return this.eddn.export_journal_scanbarycentre(cmdr, is_beta, entry) + if entry['event'].lower() == 'navroute': + return this.eddn.export_journal_navroute(cmdr, is_beta, dict(entry)) + # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and (entry['event'] in ('Location', 'FSDJump', 'Docked', 'Scan', 'SAASignalsFound', 'CarrierJump')) and From ba20599511baee903206b90084a2adfe93b03883 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 27 Sep 2021 14:46:18 +0100 Subject: [PATCH 258/316] EDDN: Make comment about removing odyssey flag a WORKAROUND This is for the NavRoute handling. --- plugins/eddn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 46c011a3..35685646 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -883,7 +883,7 @@ Msg:\n{msg}''' ####################################################################### # Elisions ####################################################################### - # This will reject with the Odyssey flag present + # WORKAROUND WIP EDDN schema | 2021-09-27: This will reject with the Odyssey flag present if entry.get('odyssey') is not None: del entry['odyssey'] ####################################################################### From e1d547460be8ded08e2671bb1513e1a375690efc Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 27 Sep 2021 14:50:11 +0100 Subject: [PATCH 259/316] EDDN: Add type hints back to new `export_journal_entry()` --- plugins/eddn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 35685646..bee12a35 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -602,7 +602,7 @@ Msg:\n{msg}''' # this.shipyard = (horizons, shipyard) - def export_journal_entry(self, cmdr, entry, msg): + def export_journal_entry(self, cmdr: str, entry: Mapping[str, Any], msg: Mapping[str, Any]) -> None: """ Update EDDN with an event from the journal. From 84ef77197df8235979b7677e9789bce9dfd11956 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 27 Sep 2021 14:51:10 +0100 Subject: [PATCH 260/316] EDDN: Use MutableMapping when needing to update a Mapping, not Dict --- plugins/eddn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index bee12a35..471e7ef7 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -847,7 +847,7 @@ Msg:\n{msg}''' return None def export_journal_navroute( - self, cmdr: str, is_beta: bool, entry: Dict[str, Any] + self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] ) -> Optional[str]: """ Send a NavRoute to EDDN on the correct schema. @@ -1140,7 +1140,7 @@ def journal_entry( # noqa: C901, CCR001 return this.eddn.export_journal_scanbarycentre(cmdr, is_beta, entry) if entry['event'].lower() == 'navroute': - return this.eddn.export_journal_navroute(cmdr, is_beta, dict(entry)) + return this.eddn.export_journal_navroute(cmdr, is_beta, entry) # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and From 554692677658c31fccc33832fcd9d39d8b40b1c7 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 27 Sep 2021 14:53:55 +0100 Subject: [PATCH 261/316] EDDN: Use 'if key (not) in dict:' rather than `.get() is (not) None` --- plugins/eddn.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 471e7ef7..e5b6da05 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -656,17 +656,17 @@ Msg:\n{msg}''' """ # If 'SystemName' or 'System' is there, it's directly from a journal event. # If they're not there *and* 'StarSystem' isn't either, then we add the latter. - if entry.get('SystemName') is None and entry.get('System') is None and entry.get('StarSystem') is None: + if 'SystemName' not in entry and 'System' not in entry and 'StarSystem' not in entry: entry['StarSystem'] = system_name - if entry.get('SystemAddress') is None: + if 'SystemAddress' not in entry: if this.systemaddress is None: logger.warning("this.systemaddress is None, can't add SystemAddress") return "this.systemaddress is None, can't add SystemAddress" entry['SystemAddress'] = this.systemaddress - if entry.get('StarPos') is None: + if 'StarPos' not in entry: if not this.coordinates: logger.warning("this.coordinates is None, can't add StarPos") return "this.coordinates is None, can't add StarPos" @@ -777,7 +777,7 @@ Msg:\n{msg}''' entry = filter_localised(entry) # Keys specific to this event for k in ('IsNewEntry', 'NewTraitsDiscovered'): - if entry.get(k) is not None: + if k in entry: del entry[k] ####################################################################### @@ -884,7 +884,7 @@ Msg:\n{msg}''' # Elisions ####################################################################### # WORKAROUND WIP EDDN schema | 2021-09-27: This will reject with the Odyssey flag present - if entry.get('odyssey') is not None: + if 'odyssey' in entry: del entry['odyssey'] ####################################################################### From b82d25ee07ab73fe6069232e872cc13682a42000 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 10 Oct 2021 14:31:40 +0100 Subject: [PATCH 262/316] EDDN: Start tracking Status.json BodyName --- plugins/eddn.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index e5b6da05..99a8f12b 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -53,6 +53,8 @@ class This: self.systemaddress: Optional[str] = None self.coordinates: Optional[Tuple] = None self.planet: Optional[str] = None + # Track Status.json data + self.status_bodyname: Optional[str] = None # Avoid duplicates self.marketId: Optional[str] = None @@ -1337,3 +1339,18 @@ def is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY logger.error(f'ships type is {type(ships)}') return economies_colony or modules_horizons or ship_horizons + + +def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]) -> None: + """ + Process Status.json data to track things like current Body. + + :param cmdr: Current Commander name. + :param is_beta: Whether non-live game version was detected. + :param entry: The latest Status.json data. + """ + if 'BodyName' in entry: + this.status_bodyname = entry['BodyName'] + + else: + this.status_bodyname = None From 870086872496ed0793fd847b904ce23935b616ab Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 10 Oct 2021 14:32:12 +0100 Subject: [PATCH 263/316] EDDN: Rename this.planet to this.bodyname for consistency --- plugins/eddn.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 99a8f12b..e97c170a 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -52,7 +52,7 @@ class This: # Track location to add to Journal events self.systemaddress: Optional[str] = None self.coordinates: Optional[Tuple] = None - self.planet: Optional[str] = None + self.bodyname: Optional[str] = None # Track Status.json data self.status_bodyname: Optional[str] = None @@ -1108,10 +1108,10 @@ def journal_entry( # noqa: C901, CCR001 # Track location if entry['event'] in ('Location', 'FSDJump', 'Docked', 'CarrierJump'): if entry['event'] in ('Location', 'CarrierJump'): - this.planet = entry.get('Body') if entry.get('BodyType') == 'Planet' else None + this.bodyname = entry.get('Body') if entry.get('BodyType') == 'Planet' else None elif entry['event'] == 'FSDJump': - this.planet = None + this.bodyname = None if 'StarPos' in entry: this.coordinates = tuple(entry['StarPos']) @@ -1122,10 +1122,10 @@ def journal_entry( # noqa: C901, CCR001 this.systemaddress = entry.get('SystemAddress') # type: ignore elif entry['event'] == 'ApproachBody': - this.planet = entry['Body'] + this.bodyname = entry['Body'] elif entry['event'] in ('LeaveBody', 'SupercruiseEntry'): - this.planet = None + this.bodyname = None # Events with their own EDDN schema if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: @@ -1176,8 +1176,8 @@ def journal_entry( # noqa: C901, CCR001 ] # add planet to Docked event for planetary stations if known - if entry['event'] == 'Docked' and this.planet: - entry['Body'] = this.planet + if entry['event'] == 'Docked' and this.bodyname: + entry['Body'] = this.bodyname entry['BodyType'] = 'Planet' # add mandatory StarSystem, StarPos and SystemAddress properties to Scan events From 50f03ae7536be69a24a8ce302507afda6337a75b Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 10 Oct 2021 14:37:20 +0100 Subject: [PATCH 264/316] EDDN: codexentry: Set BodyName if available from Status.json --- plugins/eddn.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index e97c170a..dc326653 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -647,7 +647,11 @@ Msg:\n{msg}''' } this.eddn.export_journal_entry(cmdr, entry, msg) - def entry_augment_system_data(self, entry: Dict[str, Any], system_name: str) -> Union[str, Mapping[str, Any]]: + def entry_augment_system_data( + self, + entry: MutableMapping[str, Any], + system_name: str + ) -> Union[str, MutableMapping[str, Any]]: """ Augment a journal entry with necessary system data. @@ -747,7 +751,7 @@ Msg:\n{msg}''' return None def export_journal_codexentry( - self, cmdr: str, is_beta: bool, entry: Mapping[str, Any] + self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] ) -> Optional[str]: """ Send a CodexEntry to EDDN on the correct schema. @@ -786,11 +790,16 @@ Msg:\n{msg}''' ####################################################################### # Augmentations ####################################################################### + # General 'system' augmentations ret = this.eddn.entry_augment_system_data(entry, entry['System']) if isinstance(ret, str): return ret entry = ret + + # Body Name and ID, if available + if this.status_bodyname is not None: + entry['BodyName'] = this.status_bodyname ####################################################################### msg = { From d7eacd253cbc9b930a1f98576e3d303b8dd11877 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 10 Oct 2021 14:43:01 +0100 Subject: [PATCH 265/316] EDDN: Track BodyID from Journal events * Also renames all of the body name/ID 'this' variables to snake case. --- plugins/eddn.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index dc326653..d22b451d 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -52,9 +52,10 @@ class This: # Track location to add to Journal events self.systemaddress: Optional[str] = None self.coordinates: Optional[Tuple] = None - self.bodyname: Optional[str] = None + self.body_name: Optional[str] = None + self.body_id: Optional[int] = None # Track Status.json data - self.status_bodyname: Optional[str] = None + self.status_body_name: Optional[str] = None # Avoid duplicates self.marketId: Optional[str] = None @@ -798,8 +799,8 @@ Msg:\n{msg}''' entry = ret # Body Name and ID, if available - if this.status_bodyname is not None: - entry['BodyName'] = this.status_bodyname + if this.status_body_name is not None: + entry['BodyName'] = this.status_body_name ####################################################################### msg = { @@ -1117,10 +1118,15 @@ def journal_entry( # noqa: C901, CCR001 # Track location if entry['event'] in ('Location', 'FSDJump', 'Docked', 'CarrierJump'): if entry['event'] in ('Location', 'CarrierJump'): - this.bodyname = entry.get('Body') if entry.get('BodyType') == 'Planet' else None + if entry.get('BodyType') == 'Planet': + this.body_name = entry.get('Body') + this.body_id = entry.get('BodyID') + + else: + this.body_name = None elif entry['event'] == 'FSDJump': - this.bodyname = None + this.body_name = None if 'StarPos' in entry: this.coordinates = tuple(entry['StarPos']) @@ -1131,10 +1137,10 @@ def journal_entry( # noqa: C901, CCR001 this.systemaddress = entry.get('SystemAddress') # type: ignore elif entry['event'] == 'ApproachBody': - this.bodyname = entry['Body'] + this.body_name = entry['Body'] elif entry['event'] in ('LeaveBody', 'SupercruiseEntry'): - this.bodyname = None + this.body_name = None # Events with their own EDDN schema if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: @@ -1185,8 +1191,8 @@ def journal_entry( # noqa: C901, CCR001 ] # add planet to Docked event for planetary stations if known - if entry['event'] == 'Docked' and this.bodyname: - entry['Body'] = this.bodyname + if entry['event'] == 'Docked' and this.body_name: + entry['Body'] = this.body_name entry['BodyType'] = 'Planet' # add mandatory StarSystem, StarPos and SystemAddress properties to Scan events @@ -1359,7 +1365,7 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]) -> None: :param entry: The latest Status.json data. """ if 'BodyName' in entry: - this.status_bodyname = entry['BodyName'] + this.status_body_name = entry['BodyName'] else: - this.status_bodyname = None + this.status_body_name = None From 15ea858c6b777d8c98a7cf45393c3dad3438d44d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 10 Oct 2021 14:55:46 +0100 Subject: [PATCH 266/316] EDDN: codexentry: Use BodyID if available --- plugins/eddn.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index d22b451d..48ed2b9f 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -799,8 +799,13 @@ Msg:\n{msg}''' entry = ret # Body Name and ID, if available + # We only trust the Status.json value if this.status_body_name is not None: entry['BodyName'] = this.status_body_name + # And thus only set BodyID, which will be from journal events, if + # the Journal-sourced Body Name matches + if this.status_body_name == this.body_name: + entry['BodyID'] = this.body_id ####################################################################### msg = { From 72f584481af71666a59c3ad978b3f43a961be7b4 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 10 Oct 2021 15:01:16 +0100 Subject: [PATCH 267/316] EDDN: Add --eddn-tracking-ui CL arg This will only work on startup for now, with it present/active the EDDN plugin will add a UI frame to show what it's tracking. Intended for developer use only. --- EDMarketConnector.py | 6 ++++++ config.py | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 21edf222..95426754 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -155,6 +155,12 @@ if __name__ == '__main__': # noqa: C901 '--eddn-url', help='Specify an alternate EDDN upload URL', ) + + parser.add_argument( + '--eddn-tracking-ui', + help='Have EDDN plugin show what it is tracking', + action='store_true', + ) ########################################################################### args = parser.parse_args() diff --git a/config.py b/config.py index 7a51547a..05cacd89 100644 --- a/config.py +++ b/config.py @@ -208,6 +208,7 @@ class AbstractConfig(abc.ABC): __auth_force_localserver = False # Should we use localhost for auth callback ? __auth_force_edmc_protocol = False # Should we force edmc:// protocol ? __eddn_url = None # Non-default EDDN URL + __eddn_tracking_ui = False # Show EDDN tracking UI ? def __init__(self) -> None: self.home_path = pathlib.Path.home() @@ -264,6 +265,19 @@ class AbstractConfig(abc.ABC): """ return self.__eddn_url + def set_eddn_tracking_ui(self): + """Activate EDDN tracking UI.""" + self.__eddn_tracking_ui = True + + @property + def eddn_tracking_ui(self) -> bool: + """ + Determine if the EDDN tracking UI be shown. + + :return: bool - Should tracking UI be active? + """ + return self.__eddn_tracking_ui + @property def app_dir(self) -> str: """Return a string version of app_dir.""" From dd8ec061d300781ceac821b3a62db194ce1acd52 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 10 Oct 2021 15:40:41 +0100 Subject: [PATCH 268/316] EDDN: Dummy tracking UI to determine layout --- EDMarketConnector.py | 3 +++ plugins/eddn.py | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 95426754..593d3adf 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -189,6 +189,9 @@ if __name__ == '__main__': # noqa: C901 if args.eddn_url: config.set_eddn_url(args.eddn_url) + if args.eddn_tracking_ui: + config.set_eddn_tracking_ui() + if args.force_edmc_protocol: if sys.platform == 'win32': config.set_auth_force_edmc_protocol() diff --git a/plugins/eddn.py b/plugins/eddn.py index 48ed2b9f..28c65d95 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -79,6 +79,12 @@ class This: self.eddn_delay: tk.IntVar self.eddn_delay_button: nb.Checkbutton + # Tracking UI + self.ui: Optional[tk.Frame] = None + self.ui_j_body_name: Optional[tk.Label] = None + self.ui_j_body_id: Optional[tk.Label] = None + self.ui_s_body_name: Optional[tk.Label] = None + this = This() @@ -942,7 +948,7 @@ def plugin_start3(plugin_dir: str) -> str: return 'EDDN' -def plugin_app(parent: tk.Tk) -> None: +def plugin_app(parent: tk.Tk) -> Optional[tk.Frame]: """ Set up any plugin-specific UI. @@ -953,6 +959,7 @@ def plugin_app(parent: tk.Tk) -> None: necessary. :param parent: tkinter parent frame. + :return: Optional tk.Frame, if the tracking UI is active. """ this.parent = parent this.eddn = EDDN(parent) @@ -961,6 +968,32 @@ def plugin_app(parent: tk.Tk) -> None: # Shouldn't happen - don't bother localizing this.parent.children['status']['text'] = 'Error: Is another copy of this app already running?' + if config.eddn_tracking_ui: + this.ui = tk.Frame(parent) + + row = this.ui.grid_size()[1] + journal_body_name_label = tk.Label(this.ui, text="J:BodyName:") + journal_body_name_label.grid(row=row, column=0, sticky=tk.W) + this.ui_j_body_name = tk.Label(this.ui, text='<>') + this.ui_j_body_name.grid(row=row, column=1, sticky=tk.E) + row += 1 + + journal_body_id_label = tk.Label(this.ui, text="J:BodyID:") + journal_body_id_label.grid(row=row, column=0, sticky=tk.W) + this.ui_j_body_id = tk.Label(this.ui, text='<>') + this.ui_j_body_id.grid(row=row, column=1, sticky=tk.E) + row += 1 + + status_body_name_label = tk.Label(this.ui, text="S:BodyName:") + status_body_name_label.grid(row=row, column=0, sticky=tk.W) + this.ui_s_body_name = tk.Label(this.ui, text='<>') + this.ui_s_body_name.grid(row=row, column=1, sticky=tk.E) + row += 1 + + return this.ui + + return None + def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: """ From 1637dc0f5fb3c65cf5b1a5524d1913eb307682fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:03:22 +0000 Subject: [PATCH 269/316] build(deps-dev): bump pywin32 from 301 to 302 Bumps [pywin32](https://github.com/mhammond/pywin32) from 301 to 302. - [Release notes](https://github.com/mhammond/pywin32/releases) - [Changelog](https://github.com/mhammond/pywin32/blob/main/CHANGES.txt) - [Commits](https://github.com/mhammond/pywin32/commits) --- updated-dependencies: - dependency-name: pywin32 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a1aebfb7..54469037 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -35,7 +35,7 @@ pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support coverage[toml]==6.0.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. -pywin32==301; sys_platform == 'win32' +pywin32==302; sys_platform == 'win32' # All of the normal requirements From 7be9bd1883004202d2ddd782b459be74bf82fa3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:03:25 +0000 Subject: [PATCH 270/316] build(deps-dev): bump flake8-comprehensions from 3.6.1 to 3.7.0 Bumps [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) from 3.6.1 to 3.7.0. - [Release notes](https://github.com/adamchainz/flake8-comprehensions/releases) - [Changelog](https://github.com/adamchainz/flake8-comprehensions/blob/main/HISTORY.rst) - [Commits](https://github.com/adamchainz/flake8-comprehensions/compare/3.6.1...3.7.0) --- updated-dependencies: - dependency-name: flake8-comprehensions dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a1aebfb7..5d2846a7 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ wheel flake8==3.9.2 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 -flake8-comprehensions==3.6.1 +flake8-comprehensions==3.7.0 flake8-docstrings==1.6.0 isort==5.9.3 flake8-isort==4.0.0 From 91ff8a5d3d5766a890b9d1fc2cc346ef3dcc61ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:03:32 +0000 Subject: [PATCH 271/316] build(deps): bump certifi from 2021.5.30 to 2021.10.8 Bumps [certifi](https://github.com/certifi/python-certifi) from 2021.5.30 to 2021.10.8. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2021.05.30...2021.10.08) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 82a49865..01e635c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -certifi==2021.5.30 +certifi==2021.10.8 requests==2.26.0 watchdog==2.1.6 # Commented out because this doesn't package well with py2exe From c36cc6203994a0ee14f7a61f1e582f7fbbbbcabd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:05:23 +0000 Subject: [PATCH 272/316] build(deps-dev): bump flake8 from 3.9.2 to 4.0.1 Bumps [flake8](https://github.com/pycqa/flake8) from 3.9.2 to 4.0.1. - [Release notes](https://github.com/pycqa/flake8/releases) - [Commits](https://github.com/pycqa/flake8/compare/3.9.2...4.0.1) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 216a0016..0c64ab59 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ wheel # Static analysis tools -flake8==3.9.2 +flake8==4.0.1 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.7.0 From 2ef9d9e3c3a5ac30ae6bb884bb772923eda30a12 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 12 Oct 2021 13:06:38 +0100 Subject: [PATCH 273/316] EDDN: BodyName/ID tracking tested for simple body * The Tracking UI is now up and running. * Some of the tracking corrected: - Set BodyID from ApproachBody. - Do not clear values on SupercruiseEntry, allow either FSDJump or LeaveBody to do this. - Clear the values if we detect the Main Menu music track (when you exit to the menu, not just when you look at the menu whilst in-game). * Various comments added to help with documenting the tracking for EDDN uploaders. --- plugins/eddn.py | 66 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 28c65d95..1225cd59 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -80,10 +80,10 @@ class This: self.eddn_delay_button: nb.Checkbutton # Tracking UI - self.ui: Optional[tk.Frame] = None - self.ui_j_body_name: Optional[tk.Label] = None - self.ui_j_body_id: Optional[tk.Label] = None - self.ui_s_body_name: Optional[tk.Label] = None + self.ui: tk.Frame + self.ui_j_body_name: tk.Label + self.ui_j_body_id: tk.Label + self.ui_s_body_name: tk.Label this = This() @@ -805,13 +805,14 @@ Msg:\n{msg}''' entry = ret # Body Name and ID, if available - # We only trust the Status.json value - if this.status_body_name is not None: + # Status.json BodyName is set as soon as the Orbital Cruise/Glide HUD + # activates. + # Journal ApproachBody only occurs once inside Orbital Cruise altitude. + # So we check that both are set, and match, then use the Status.json + # value. + if this.status_body_name is not None and this.status_body_name == this.body_name: entry['BodyName'] = this.status_body_name - # And thus only set BodyID, which will be from journal events, if - # the Journal-sourced Body Name matches - if this.status_body_name == this.body_name: - entry['BodyID'] = this.body_id + entry['BodyID'] = this.body_id ####################################################################### msg = { @@ -974,19 +975,19 @@ def plugin_app(parent: tk.Tk) -> Optional[tk.Frame]: row = this.ui.grid_size()[1] journal_body_name_label = tk.Label(this.ui, text="J:BodyName:") journal_body_name_label.grid(row=row, column=0, sticky=tk.W) - this.ui_j_body_name = tk.Label(this.ui, text='<>') + this.ui_j_body_name = tk.Label(this.ui, name='eddn_track_j_body_name', anchor=tk.W) this.ui_j_body_name.grid(row=row, column=1, sticky=tk.E) row += 1 journal_body_id_label = tk.Label(this.ui, text="J:BodyID:") journal_body_id_label.grid(row=row, column=0, sticky=tk.W) - this.ui_j_body_id = tk.Label(this.ui, text='<>') + this.ui_j_body_id = tk.Label(this.ui, name='eddn_track_j_body_id', anchor=tk.W) this.ui_j_body_id.grid(row=row, column=1, sticky=tk.E) row += 1 status_body_name_label = tk.Label(this.ui, text="S:BodyName:") status_body_name_label.grid(row=row, column=0, sticky=tk.W) - this.ui_s_body_name = tk.Label(this.ui, text='<>') + this.ui_s_body_name = tk.Label(this.ui, name='eddn_track_s_body_name', anchor=tk.W) this.ui_s_body_name.grid(row=row, column=1, sticky=tk.E) row += 1 @@ -995,6 +996,26 @@ def plugin_app(parent: tk.Tk) -> Optional[tk.Frame]: return None +def tracking_ui_update() -> None: + """Update the Tracking UI with current data.""" + if this.body_name is None: + this.ui_j_body_name['text'] = '≪None≫' + else: + this.ui_j_body_name['text'] = this.body_name + + if this.body_id is None: + this.ui_j_body_id['text'] = '≪None≫' + else: + this.ui_j_body_id['text'] = str(this.body_id) + + if this.status_body_name is None: + this.ui_s_body_name['text'] = '≪None≫' + else: + this.ui_s_body_name['text'] = this.status_body_name + + this.ui.update_idletasks() + + def plugin_prefs(parent, cmdr: str, is_beta: bool) -> Frame: """ Set up Preferences pane for this plugin. @@ -1165,6 +1186,7 @@ def journal_entry( # noqa: C901, CCR001 elif entry['event'] == 'FSDJump': this.body_name = None + this.body_id = None if 'StarPos' in entry: this.coordinates = tuple(entry['StarPos']) @@ -1176,9 +1198,21 @@ def journal_entry( # noqa: C901, CCR001 elif entry['event'] == 'ApproachBody': this.body_name = entry['Body'] + this.body_id = entry.get('BodyID') - elif entry['event'] in ('LeaveBody', 'SupercruiseEntry'): + elif entry['event'] == 'LeaveBody': + # NB: **NOT** SupercruiseEntry, because we won't get a fresh + # ApproachBody if we don't leave Orbital Cruise and land again. + # *This* is triggered when you go above Orbital Cruise altitude. + # Status.json BodyName clears when the OC/Glide HUD is deactivated. this.body_name = None + this.body_id = None + + elif entry['event'] == 'Music': + if entry['MusicTrack'] == 'MainMenu': + this.body_name = None + this.body_id = None + this.status_body_name = None # Events with their own EDDN schema if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: @@ -1307,6 +1341,8 @@ def journal_entry( # noqa: C901, CCR001 logger.debug(f'Failed exporting {entry["event"]}', exc_info=e) return str(e) + tracking_ui_update() + return None @@ -1407,3 +1443,5 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]) -> None: else: this.status_body_name = None + + tracking_ui_update() From 9a79c084a87cee90dcf6936bcbc41b5b3809b5ef Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 12 Oct 2021 13:12:21 +0100 Subject: [PATCH 274/316] requirements-dev: Step flake8 back down to 3.9.2 for now flake8-isort needs to publish a release that allows for >= 4 --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0c64ab59..216a0016 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ wheel # Static analysis tools -flake8==4.0.1 +flake8==3.9.2 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.7.0 From a949b408a3735fea4d4b16da827703c263a6cd6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Oct 2021 17:04:45 +0000 Subject: [PATCH 275/316] build(deps-dev): bump types-requests from 2.25.9 to 2.25.10 Bumps [types-requests](https://github.com/python/typeshed) from 2.25.9 to 2.25.10. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 216a0016..5acb8c76 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.1 mypy==0.910 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.25.9 +types-requests==2.25.10 # Code formatting tools autopep8==1.5.7 From 24597e78e925a44ffe1517911649fb6edf1296b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Oct 2021 17:04:54 +0000 Subject: [PATCH 276/316] build(deps-dev): bump coverage[toml] from 6.0.1 to 6.0.2 Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/6.0.1...6.0.2) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 216a0016..9e8f60c9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -33,7 +33,7 @@ py2exe==0.10.4.1; sys_platform == 'win32' # Testing pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support -coverage[toml]==6.0.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==6.0.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. pywin32==302; sys_platform == 'win32' From b551af45b8ea13a21adb75b160537e882ec0f250 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Oct 2021 17:04:56 +0000 Subject: [PATCH 277/316] build(deps-dev): bump flake8-noqa from 1.1.0 to 1.2.0 Bumps [flake8-noqa](https://github.com/plinss/flake8-noqa) from 1.1.0 to 1.2.0. - [Release notes](https://github.com/plinss/flake8-noqa/releases) - [Commits](https://github.com/plinss/flake8-noqa/compare/v1.1.0...v1.2.0) --- updated-dependencies: - dependency-name: flake8-noqa dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 216a0016..bebc2799 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,7 @@ flake8-docstrings==1.6.0 isort==5.9.3 flake8-isort==4.0.0 flake8-json==21.7.0 -flake8-noqa==1.1.0 +flake8-noqa==1.2.0 flake8-polyfill==1.0.2 flake8-use-fstring==1.1 From 60f3b524e2a644cb8afe07ef8595b3bc55b0636d Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 14 Oct 2021 13:04:52 +0100 Subject: [PATCH 278/316] EDDN: codexentry: Always set BodyName from Status.json, ID if Journal matches Partial data is better than no data, or bad data. So if Status.json BodyName is available, always use it. *Then* see if Journal BodyName matches that, and if so set BodyID. Close-orbiting bodies might therefore cause BodyName to be set without BodyID, but then it's up to EDDN listeners to make a decision, rather than making it for them. --- plugins/eddn.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 1225cd59..a7afd2a9 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -804,15 +804,13 @@ Msg:\n{msg}''' entry = ret - # Body Name and ID, if available - # Status.json BodyName is set as soon as the Orbital Cruise/Glide HUD - # activates. - # Journal ApproachBody only occurs once inside Orbital Cruise altitude. - # So we check that both are set, and match, then use the Status.json - # value. - if this.status_body_name is not None and this.status_body_name == this.body_name: + # Set BodyName if it's available from Status.json + if this.status_body_name is not None: entry['BodyName'] = this.status_body_name - entry['BodyID'] = this.body_id + # Only set BodyID if journal BodyName matches the Status.json one. + # This avoids binary body issues. + if this.status_body_name == this.body_name: + entry['BodyID'] = this.body_id ####################################################################### msg = { From 86d4369e0f8177ab1abef9663dd034a1672f086a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 17:03:09 +0000 Subject: [PATCH 279/316] build(deps-dev): bump flake8-isort from 4.0.0 to 4.1.1 Bumps [flake8-isort](https://github.com/gforcada/flake8-isort) from 4.0.0 to 4.1.1. - [Release notes](https://github.com/gforcada/flake8-isort/releases) - [Changelog](https://github.com/gforcada/flake8-isort/blob/master/CHANGES.rst) - [Commits](https://github.com/gforcada/flake8-isort/compare/4.0.0...4.1.1) --- updated-dependencies: - dependency-name: flake8-isort dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 714a1246..8a2d252c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.7.0 flake8-docstrings==1.6.0 isort==5.9.3 -flake8-isort==4.0.0 +flake8-isort==4.1.1 flake8-json==21.7.0 flake8-noqa==1.2.0 flake8-polyfill==1.0.2 From 214032e8e70182c97fd630924fb2411a583ba00b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Oct 2021 17:07:24 +0000 Subject: [PATCH 280/316] build(deps-dev): bump flake8 from 3.9.2 to 4.0.1 Bumps [flake8](https://github.com/pycqa/flake8) from 3.9.2 to 4.0.1. - [Release notes](https://github.com/pycqa/flake8/releases) - [Commits](https://github.com/pycqa/flake8/compare/3.9.2...4.0.1) --- updated-dependencies: - dependency-name: flake8 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8a2d252c..b59f0999 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ wheel # Static analysis tools -flake8==3.9.2 +flake8==4.0.1 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.7.0 From 8ecb01c5ae8649c2cca13564a29996b42e73d417 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Thu, 14 Oct 2021 18:13:21 +0100 Subject: [PATCH 281/316] Revert "build(deps-dev): bump flake8 from 3.9.2 to 4.0.1" This reverts commit 214032e8e70182c97fd630924fb2411a583ba00b. flake8-use-fstring has `flake8==3.*` --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b59f0999..8a2d252c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ wheel # Static analysis tools -flake8==4.0.1 +flake8==3.9.2 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.7.0 From 42594c93661956ebced19418dc70132f03b79f7b Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Fri, 15 Oct 2021 11:57:09 +0100 Subject: [PATCH 282/316] EDDN: Abbreviate tracking_ui_update() code --- plugins/eddn.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index a7afd2a9..e6aa9431 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -996,19 +996,16 @@ def plugin_app(parent: tk.Tk) -> Optional[tk.Frame]: def tracking_ui_update() -> None: """Update the Tracking UI with current data.""" - if this.body_name is None: - this.ui_j_body_name['text'] = '≪None≫' - else: + this.ui_j_body_name['text'] = '≪None≫' + if this.body_name is not None: this.ui_j_body_name['text'] = this.body_name - if this.body_id is None: - this.ui_j_body_id['text'] = '≪None≫' - else: + this.ui_j_body_id['text'] = '≪None≫' + if this.body_id is not None: this.ui_j_body_id['text'] = str(this.body_id) - if this.status_body_name is None: - this.ui_s_body_name['text'] = '≪None≫' - else: + this.ui_s_body_name['text'] = '≪None≫' + if this.status_body_name is not None: this.ui_s_body_name['text'] = this.status_body_name this.ui.update_idletasks() From 92054f4e1514fd4b4dfdd56c92419f15e8ad6c4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 17:03:12 +0000 Subject: [PATCH 283/316] build(deps): bump actions/checkout from 2.3.4 to 2.3.5 Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.3.5) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .github/workflows/pr-checks.yml | 2 +- .github/workflows/push-checks.yml | 2 +- .github/workflows/windows-build.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 318ff80f..03dc1744 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -28,7 +28,7 @@ jobs: # based on. - name: Checkout head commits # https://github.com/actions/checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.3.5 #with: #ref: ${{github.head.sha}} #repository: ${{github.event.pull_request.head.repo.full_name}} diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index 8438a9a2..8c7f54b3 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 with: fetch-depth: 0 - name: Set up Python 3.9 diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index eac27cf4..06c1c027 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -16,7 +16,7 @@ jobs: shell: powershell steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - uses: actions/setup-python@v2.2.2 with: python-version: "3.9.7" From 65122ea3e7830159b44af4927eadd970bc842ca8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Oct 2021 17:03:20 +0000 Subject: [PATCH 284/316] build(deps-dev): bump types-requests from 2.25.10 to 2.25.11 Bumps [types-requests](https://github.com/python/typeshed) from 2.25.10 to 2.25.11. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8a2d252c..152c5507 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.1 mypy==0.910 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.25.10 +types-requests==2.25.11 # Code formatting tools autopep8==1.5.7 From 1f7b0b7c0d42c09a4c64384ac4520b897f75934b Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 14:02:10 +0100 Subject: [PATCH 285/316] companion.py: Fix isort order complaint --- companion.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 16839925..1a811858 100644 --- a/companion.py +++ b/companion.py @@ -29,12 +29,11 @@ from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, OrderedDic import requests import config as conf_module +import protocol from config import appname, appversion, config from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor -# from protocol import protocolhandler -import protocol logger = get_main_logger() From 62b5e4e7eb3491a9e5b5095d22f8e322c25e2bd0 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 14:03:07 +0100 Subject: [PATCH 286/316] EDMarketConnector.py: Fix isort order complaint --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b092c442..4c480f47 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -376,6 +376,7 @@ from tkinter import ttk import commodity import plug import prefs +import protocol import stats import td from commodity import COMMODITY_CSV @@ -384,7 +385,6 @@ from edmc_data import ship_name_map from hotkey import hotkeymgr from l10n import Translations from monitor import monitor -import protocol from theme import theme from ttkHyperlinkLabel import HyperlinkLabel From fc34d023b6db4255504d16e7d4317caccb695003 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 14:10:30 +0100 Subject: [PATCH 287/316] EDDN: tracking_ui_update(): check if this UI bit actually active --- plugins/eddn.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index e6aa9431..841cbead 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -995,7 +995,10 @@ def plugin_app(parent: tk.Tk) -> Optional[tk.Frame]: def tracking_ui_update() -> None: - """Update the Tracking UI with current data.""" + """Update the Tracking UI with current data, if required.""" + if not config.eddn_tracking_ui: + return + this.ui_j_body_name['text'] = '≪None≫' if this.body_name is not None: this.ui_j_body_name['text'] = this.body_name From 5ae953bf4559d86541e20fe3bd882eee15cfaae5 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 14:24:23 +0100 Subject: [PATCH 288/316] EDDN: Drop messages if EDDN says it doesn't know the schema Currently Live EDDN doesn't yet have the new schemas such as navroute, so will complain. Rather than leaving such messages in our EDDN replay queue and getting an error every time that is run, detect this error case and drop the messages. --- plugins/eddn.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 841cbead..9a783936 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -106,6 +106,11 @@ class EDDN: TIMEOUT = 10 # requests timeout MODULE_RE = re.compile(r'^Hpt_|^Int_|Armour_', re.IGNORECASE) CANONICALISE_RE = re.compile(r'\$(.+)_name;') + UNKNOWN_SCHEMA_RE = re.compile( + r"^FAIL: \[JsonValidationException\('Schema " + r"https://eddn.edcd.io/schemas/(?P<schema_name>.+)/(?P<schema_version>[0-9]+) is unknown, " + r"unable to validate.',\)\]$" + ) def __init__(self, parent: tk.Tk): self.parent: tk.Tk = parent @@ -210,6 +215,11 @@ class EDDN: logger.trace_if('plugin.eddn', "EDDN is still objecting to empty commodities data") return # We want to silence warnings otherwise + if unknown_schema := self.UNKNOWN_SCHEMA_RE.match(r.text): + logger.warning(f"EDDN doesn't (yet?) know about schema: {unknown_schema['schema_name']}" + f"/{unknown_schema['schema_version']}") + return # Pretend it went OK so this message isn't retried + logger.debug( f'''Status from POST wasn't OK: Status\t{r.status_code} From 7fa0576c85763205cd9be5f3ec873d06ff8cc1cf Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 14:41:25 +0100 Subject: [PATCH 289/316] EDDN: Keep 'unknown schema' messages in replaylog This still logs them, at DEBUG, which will get spammy as such messages accumulate. Uncommenting the: `# return # Pretend it went OK so this message isn't retried` at line 291 in EDDN.sendreplay(), in the: `except requests.exceptions.HTTPError as e:` block would cause them to not stay in the replaylog. --- plugins/eddn.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 9a783936..66956c48 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -215,19 +215,15 @@ class EDDN: logger.trace_if('plugin.eddn', "EDDN is still objecting to empty commodities data") return # We want to silence warnings otherwise - if unknown_schema := self.UNKNOWN_SCHEMA_RE.match(r.text): - logger.warning(f"EDDN doesn't (yet?) know about schema: {unknown_schema['schema_name']}" - f"/{unknown_schema['schema_version']}") - return # Pretend it went OK so this message isn't retried - - logger.debug( - f'''Status from POST wasn't OK: + if not self.UNKNOWN_SCHEMA_RE.match(r.text): + logger.debug( + f'''Status from POST wasn't OK: Status\t{r.status_code} URL\t{r.url} Headers\t{r.headers} Content:\n{r.text} Msg:\n{msg}''' - ) + ) r.raise_for_status() @@ -289,7 +285,13 @@ Msg:\n{msg}''' # in question out of replaylog, else we'll keep retrying a bad message # forever. except requests.exceptions.HTTPError as e: - status['text'] = self.http_error_to_log(e) + if unknown_schema := self.UNKNOWN_SCHEMA_RE.match(e.response.text): + logger.debug(f"EDDN doesn't (yet?) know about schema: {unknown_schema['schema_name']}" + f"/{unknown_schema['schema_version']}") + # return # Pretend it went OK so this message isn't retried + + else: + status['text'] = self.http_error_to_log(e) except requests.exceptions.RequestException as e: logger.debug('Failed sending', exc_info=e) From aecf37513880ea7736e1c001226355f44067b990 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 14:55:43 +0100 Subject: [PATCH 290/316] EDDN: Record `horizons` flag in journal_entry() So that we can then first check *this* in `is_horizons()`. --- plugins/eddn.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index e6aa9431..2b42ac3d 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -46,6 +46,8 @@ class This: # Track if we're on foot self.on_foot = False + # Horizons ? + self.horizons = False # Running under Odyssey? self.odyssey = False @@ -1165,8 +1167,9 @@ def journal_entry( # noqa: C901, CCR001 this.on_foot = state['OnFoot'] - # Note if we're under Odyssey - # The only event this is already in is `LoadGame` which isn't sent to EDDN. + # Note if we're under Horizons and/or Odyssey + # The only event these are already in is `LoadGame` which isn't sent to EDDN. + this.horizons = entry['horizons'] = state['Horizons'] this.odyssey = entry['odyssey'] = state['Odyssey'] # Track location From ca80edd34d88298a2c9d933f79f0da8e68d870eb Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 15:01:51 +0100 Subject: [PATCH 291/316] EDDN: If `this.horizons` is true, use that in `is_horizons()` check --- plugins/eddn.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 2b42ac3d..497bb4f6 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1395,6 +1395,22 @@ def is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY :param ships: Ships available at the docked station. :return: bool - True if the Cmdr has Horizons access. """ + # First check the Journal-sourced flag + # NB: This assumes game currently running, rather than "old + # journal file". `LoadGame` determines monitor.cmdr, which determines + # the account used for CAPI, so it *should* match. + # + # For this to be wrong, the user would have had to have, e.g.: + # + # 1. Logged into Cmdr in Horizons/Odyssey, then back out again. + # 2. Logged into Cmdr in base game, either on another machine, or + # have removed the Journal file after. + # 3. Re-run EDMC and triggered a manual CAPI update. + # + # This seems unlikely. + if this.horizons: + return True + economies_colony = False modules_horizons = False ship_horizons = False From bd183efbd4dfa8365246952f04352403195102aa Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 15:41:15 +0100 Subject: [PATCH 292/316] EDDN: Just use `this.horizons` for CAPI data sending --- plugins/eddn.py | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 90f9d505..78932c45 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -442,14 +442,6 @@ Msg:\n{msg}''' """ modules, ships = self.safe_modules_and_ships(data) - # Horizons flag - will hit at least Int_PlanetApproachSuite other than at engineer bases ("Colony"), - # prison or rescue Megaships, or under Pirate Attack etc - horizons: bool = is_horizons( - data['lastStarport'].get('economies', {}), - modules, - ships - ) - to_search: Iterator[Mapping[str, Any]] = filter( lambda m: self.MODULE_RE.search(m['name']) and m.get('sku') in (None, HORIZ_SKU) and m['name'] != 'Int_PlanetApproachSuite', @@ -461,7 +453,7 @@ Msg:\n{msg}''' ) # Don't send empty modules list - schema won't allow it - if outfitting and this.outfitting != (horizons, outfitting): + if outfitting and this.outfitting != (this.horizons, outfitting): self.send(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -469,13 +461,13 @@ Msg:\n{msg}''' ('systemName', data['lastSystem']['name']), ('stationName', data['lastStarport']['name']), ('marketId', data['lastStarport']['id']), - ('horizons', horizons), + ('horizons', this.horizons), ('modules', outfitting), ('odyssey', is_odyssey), ]), }) - this.outfitting = (horizons, outfitting) + this.outfitting = (this.horizons, outfitting) def export_shipyard(self, data: CAPIData, is_beta: bool, is_odyssey: bool) -> None: """ @@ -488,12 +480,6 @@ Msg:\n{msg}''' """ modules, ships = self.safe_modules_and_ships(data) - horizons: bool = is_horizons( - data['lastStarport'].get('economies', {}), - modules, - ships - ) - shipyard: List[Mapping[str, Any]] = sorted( itertools.chain( (ship['name'].lower() for ship in (ships['shipyard_list'] or {}).values()), @@ -501,7 +487,7 @@ Msg:\n{msg}''' ) ) # Don't send empty ships list - shipyard data is only guaranteed present if user has visited the shipyard. - if shipyard and this.shipyard != (horizons, shipyard): + if shipyard and this.shipyard != (this.horizons, shipyard): self.send(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -509,13 +495,13 @@ Msg:\n{msg}''' ('systemName', data['lastSystem']['name']), ('stationName', data['lastStarport']['name']), ('marketId', data['lastStarport']['id']), - ('horizons', horizons), + ('horizons', this.horizons), ('ships', shipyard), ('odyssey', is_odyssey), ]), }) - this.shipyard = (horizons, shipyard) + this.shipyard = (this.horizons, shipyard) def export_journal_commodities(self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]) -> None: """ @@ -571,7 +557,6 @@ Msg:\n{msg}''' :param entry: The relevant journal entry """ modules: List[Mapping[str, Any]] = entry.get('Items', []) - horizons: bool = entry.get('Horizons', False) # outfitting = sorted([self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), module['Name']) # for module in modules if module['Name'] != 'int_planetapproachsuite']) outfitting: List[str] = sorted( @@ -579,7 +564,7 @@ Msg:\n{msg}''' filter(lambda m: m['Name'] != 'int_planetapproachsuite', modules) ) # Don't send empty modules list - schema won't allow it - if outfitting and this.outfitting != (horizons, outfitting): + if outfitting and this.outfitting != (this.horizons, outfitting): self.send(cmdr, { '$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -587,13 +572,13 @@ Msg:\n{msg}''' ('systemName', entry['StarSystem']), ('stationName', entry['StationName']), ('marketId', entry['MarketID']), - ('horizons', horizons), + ('horizons', this.horizons), ('modules', outfitting), ('odyssey', entry['odyssey']) ]), }) - this.outfitting = (horizons, outfitting) + this.outfitting = (this.horizons, outfitting) def export_journal_shipyard(self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]) -> None: """ @@ -606,10 +591,9 @@ Msg:\n{msg}''' :param entry: the relevant journal entry """ ships: List[Mapping[str, Any]] = entry.get('PriceList') or [] - horizons: bool = entry.get('Horizons', False) shipyard = sorted(ship['ShipType'] for ship in ships) # Don't send empty ships list - shipyard data is only guaranteed present if user has visited the shipyard. - if shipyard and this.shipyard != (horizons, shipyard): + if shipyard and this.shipyard != (this.horizons, shipyard): self.send(cmdr, { '$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -617,13 +601,13 @@ Msg:\n{msg}''' ('systemName', entry['StarSystem']), ('stationName', entry['StationName']), ('marketId', entry['MarketID']), - ('horizons', horizons), + ('horizons', this.horizons), ('ships', shipyard), ('odyssey', entry['odyssey']) ]), }) - # this.shipyard = (horizons, shipyard) + # this.shipyard = (this.horizons, shipyard) def export_journal_entry(self, cmdr: str, entry: Mapping[str, Any], msg: Mapping[str, Any]) -> None: """ From 556ace5306bebbcf34c1a56a9023a822218a73f1 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 15:47:12 +0100 Subject: [PATCH 293/316] EDDN: Use `this.odyssey` rather than passing to functions --- plugins/eddn.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 78932c45..11d5100d 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -329,7 +329,7 @@ Msg:\n{msg}''' # 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_code) - def export_commodities(self, data: Mapping[str, Any], is_beta: bool, is_odyssey: bool) -> None: # noqa: CCR001 + def export_commodities(self, data: Mapping[str, Any], is_beta: bool) -> None: # noqa: CCR001 """ Update EDDN with the commodities on the current (lastStarport) station. @@ -337,7 +337,6 @@ Msg:\n{msg}''' :param data: a dict containing the starport data :param is_beta: whether or not we're currently in beta mode - :param is_odyssey: whether the account shows as having Odyssey expansion. """ commodities: List[OrderedDictT[str, Any]] = [] for commodity in data['lastStarport'].get('commodities') or []: @@ -373,7 +372,7 @@ Msg:\n{msg}''' ('stationName', data['lastStarport']['name']), ('marketId', data['lastStarport']['id']), ('commodities', commodities), - ('odyssey', is_odyssey), + ('odyssey', this.odyssey), ]) if 'economies' in data['lastStarport']: @@ -431,7 +430,7 @@ Msg:\n{msg}''' return modules, ships - def export_outfitting(self, data: CAPIData, is_beta: bool, is_odyssey: bool) -> None: + def export_outfitting(self, data: CAPIData, is_beta: bool) -> None: """ Update EDDN with the current (lastStarport) station's outfitting options, if any. @@ -463,13 +462,13 @@ Msg:\n{msg}''' ('marketId', data['lastStarport']['id']), ('horizons', this.horizons), ('modules', outfitting), - ('odyssey', is_odyssey), + ('odyssey', this.odyssey), ]), }) this.outfitting = (this.horizons, outfitting) - def export_shipyard(self, data: CAPIData, is_beta: bool, is_odyssey: bool) -> None: + def export_shipyard(self, data: CAPIData, is_beta: bool) -> None: """ Update EDDN with the current (lastStarport) station's outfitting options, if any. @@ -497,7 +496,7 @@ Msg:\n{msg}''' ('marketId', data['lastStarport']['id']), ('horizons', this.horizons), ('ships', shipyard), - ('odyssey', is_odyssey), + ('odyssey', this.odyssey), ]), }) @@ -1364,9 +1363,9 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: # noqa: CCR001 status['text'] = _('Sending data to EDDN...') # LANG: Status text shown while attempting to send data status.update_idletasks() - this.eddn.export_commodities(data, is_beta, this.odyssey) - this.eddn.export_outfitting(data, is_beta, this.odyssey) - this.eddn.export_shipyard(data, is_beta, this.odyssey) + this.eddn.export_commodities(data, is_beta) + this.eddn.export_outfitting(data, is_beta) + this.eddn.export_shipyard(data, is_beta) if not old_status: status['text'] = '' status.update_idletasks() From a32546b00163c6948f43a2478905b79a906efa73 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 16:14:35 +0100 Subject: [PATCH 294/316] EDDN: Set `horizons` on `commodity/3` messages as per schema --- plugins/eddn.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 11d5100d..2ae0fac0 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -372,6 +372,7 @@ Msg:\n{msg}''' ('stationName', data['lastStarport']['name']), ('marketId', data['lastStarport']['id']), ('commodities', commodities), + ('horizons', this.horizons), ('odyssey', this.odyssey), ]) From b3419ec548fff8a97a2c5284cc5198684ca06259 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 16:17:58 +0100 Subject: [PATCH 295/316] EDDN: Also set `commodity/3`->`horizons` for journal-sourced data --- plugins/eddn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 2ae0fac0..4f20aef0 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -540,7 +540,8 @@ Msg:\n{msg}''' ('stationName', entry['StationName']), ('marketId', entry['MarketID']), ('commodities', commodities), - ('odyssey', entry['odyssey']) + ('horizons', this.horizons), + ('odyssey', this.odyssey), ]), }) From 4fef4257bd00376f45e94a35db925c0fbb58b267 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 16:20:46 +0100 Subject: [PATCH 296/316] EDDN: use this.odyssey for Journal outfitting and shipyard For consistency with the horizons flag. They *are* placed *from* the `this` versions into `entry`, but we might as well go the direct route. They have to be set explicitly here as we process the `entry` to make a wholly new `message` rather than the `message` just being `entry` with some changes. --- plugins/eddn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 4f20aef0..8c5ed1d9 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -575,7 +575,7 @@ Msg:\n{msg}''' ('marketId', entry['MarketID']), ('horizons', this.horizons), ('modules', outfitting), - ('odyssey', entry['odyssey']) + ('odyssey', this.odyssey), ]), }) @@ -604,7 +604,7 @@ Msg:\n{msg}''' ('marketId', entry['MarketID']), ('horizons', this.horizons), ('ships', shipyard), - ('odyssey', entry['odyssey']) + ('odyssey', this.odyssey), ]), }) From 29b69745f3a8f2dc9e54d88f6dd889eb7c90fcd1 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 16:32:38 +0100 Subject: [PATCH 297/316] EDDN: document that some EDDN messages aren't retried --- plugins/eddn.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 8c5ed1d9..c2163509 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -335,6 +335,10 @@ Msg:\n{msg}''' Once the send is complete, this.commodities is updated with the new data. + NB: This does *not* go through the replaylog, unlike most of the + Journal-sourced data. This kind of timely data is often rejected by + listeners if 'too old' anyway, so little point. + :param data: a dict containing the starport data :param is_beta: whether or not we're currently in beta mode """ @@ -437,6 +441,10 @@ Msg:\n{msg}''' Once the send is complete, this.outfitting is updated with the given data. + NB: This does *not* go through the replaylog, unlike most of the + Journal-sourced data. This kind of timely data is often rejected by + listeners if 'too old' anyway, so little point. + :param data: dict containing the outfitting data :param is_beta: whether or not we're currently in beta mode """ @@ -475,6 +483,10 @@ Msg:\n{msg}''' Once the send is complete, this.shipyard is updated to the new data. + NB: This does *not* go through the replaylog, unlike most of the + Journal-sourced data. This kind of timely data is often rejected by + listeners if 'too old' anyway, so little point. + :param data: dict containing the shipyard data :param is_beta: whether or not we are in beta mode """ @@ -509,6 +521,10 @@ Msg:\n{msg}''' As a side effect, it also updates this.commodities with the data. + NB: This does *not* go through the replaylog, unlike most of the + Journal-sourced data. This kind of timely data is often rejected by + listeners if 'too old' anyway, so little point. + :param cmdr: The commander to send data under :param is_beta: whether or not we're in beta mode :param entry: the journal entry containing the commodities data @@ -553,6 +569,10 @@ Msg:\n{msg}''' As a side effect, it also updates this.outfitting with the data. + NB: This does *not* go through the replaylog, unlike most of the + Journal-sourced data. This kind of timely data is often rejected by + listeners if 'too old' anyway, so little point. + :param cmdr: The commander to send data under :param is_beta: Whether or not we're in beta mode :param entry: The relevant journal entry @@ -587,6 +607,10 @@ Msg:\n{msg}''' As a side effect, this.shipyard is updated with the data. + NB: This does *not* go through the replaylog, unlike most of the + Journal-sourced data. This kind of timely data is often rejected by + listeners if 'too old' anyway, so little point. + :param cmdr: the commander to send this update under :param is_beta: Whether or not we're in beta mode :param entry: the relevant journal entry @@ -614,7 +638,8 @@ Msg:\n{msg}''' """ Update EDDN with an event from the journal. - Additionally if additional lines are cached, it may send those as well. + Additionally if other lines have been saved for retry, it may send + those as well. :param cmdr: Commander name as passed in through `journal_entry()`. :param entry: The full journal event dictionary (due to checks in this function). From 98f215e6ade7cf14766467cd2cbd3466d86689b1 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Sun, 17 Oct 2021 16:46:11 +0100 Subject: [PATCH 298/316] EDDN: Remove `horizons` flag from navroute messages As with `odyssey` this isn't currently in the not-yet-live schema. --- plugins/eddn.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index c2163509..07071a32 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -929,9 +929,14 @@ Msg:\n{msg}''' ####################################################################### # Elisions ####################################################################### - # WORKAROUND WIP EDDN schema | 2021-09-27: This will reject with the Odyssey flag present + # 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 ####################################################################### ####################################################################### From e14819797158ddced315aa3456fc491d5a695763 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 12:08:32 +0100 Subject: [PATCH 299/316] Revert "EDDN: use this.odyssey for Journal outfitting and shipyard" This reverts commit 4fef4257bd00376f45e94a35db925c0fbb58b267. --- plugins/eddn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 07071a32..70657136 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -595,7 +595,7 @@ Msg:\n{msg}''' ('marketId', entry['MarketID']), ('horizons', this.horizons), ('modules', outfitting), - ('odyssey', this.odyssey), + ('odyssey', entry['odyssey']) ]), }) @@ -628,7 +628,7 @@ Msg:\n{msg}''' ('marketId', entry['MarketID']), ('horizons', this.horizons), ('ships', shipyard), - ('odyssey', this.odyssey), + ('odyssey', entry['odyssey']) ]), }) From 90aa2698cdb62c794b372b600c457288f8c1416b Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 12:09:55 +0100 Subject: [PATCH 300/316] Revert "EDDN: Just use `this.horizons` for CAPI data sending" This reverts commit bd183efbd4dfa8365246952f04352403195102aa. --- plugins/eddn.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 70657136..751e1085 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -450,6 +450,14 @@ Msg:\n{msg}''' """ modules, ships = self.safe_modules_and_ships(data) + # Horizons flag - will hit at least Int_PlanetApproachSuite other than at engineer bases ("Colony"), + # prison or rescue Megaships, or under Pirate Attack etc + horizons: bool = is_horizons( + data['lastStarport'].get('economies', {}), + modules, + ships + ) + to_search: Iterator[Mapping[str, Any]] = filter( lambda m: self.MODULE_RE.search(m['name']) and m.get('sku') in (None, HORIZ_SKU) and m['name'] != 'Int_PlanetApproachSuite', @@ -461,7 +469,7 @@ Msg:\n{msg}''' ) # Don't send empty modules list - schema won't allow it - if outfitting and this.outfitting != (this.horizons, outfitting): + if outfitting and this.outfitting != (horizons, outfitting): self.send(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -469,13 +477,13 @@ Msg:\n{msg}''' ('systemName', data['lastSystem']['name']), ('stationName', data['lastStarport']['name']), ('marketId', data['lastStarport']['id']), - ('horizons', this.horizons), + ('horizons', horizons), ('modules', outfitting), ('odyssey', this.odyssey), ]), }) - this.outfitting = (this.horizons, outfitting) + this.outfitting = (horizons, outfitting) def export_shipyard(self, data: CAPIData, is_beta: bool) -> None: """ @@ -492,6 +500,12 @@ Msg:\n{msg}''' """ modules, ships = self.safe_modules_and_ships(data) + horizons: bool = is_horizons( + data['lastStarport'].get('economies', {}), + modules, + ships + ) + shipyard: List[Mapping[str, Any]] = sorted( itertools.chain( (ship['name'].lower() for ship in (ships['shipyard_list'] or {}).values()), @@ -499,7 +513,7 @@ Msg:\n{msg}''' ) ) # Don't send empty ships list - shipyard data is only guaranteed present if user has visited the shipyard. - if shipyard and this.shipyard != (this.horizons, shipyard): + if shipyard and this.shipyard != (horizons, shipyard): self.send(data['commander']['name'], { '$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -507,13 +521,13 @@ Msg:\n{msg}''' ('systemName', data['lastSystem']['name']), ('stationName', data['lastStarport']['name']), ('marketId', data['lastStarport']['id']), - ('horizons', this.horizons), + ('horizons', horizons), ('ships', shipyard), ('odyssey', this.odyssey), ]), }) - this.shipyard = (this.horizons, shipyard) + this.shipyard = (horizons, shipyard) def export_journal_commodities(self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]) -> None: """ @@ -578,6 +592,7 @@ Msg:\n{msg}''' :param entry: The relevant journal entry """ modules: List[Mapping[str, Any]] = entry.get('Items', []) + horizons: bool = entry.get('Horizons', False) # outfitting = sorted([self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), module['Name']) # for module in modules if module['Name'] != 'int_planetapproachsuite']) outfitting: List[str] = sorted( @@ -585,7 +600,7 @@ Msg:\n{msg}''' filter(lambda m: m['Name'] != 'int_planetapproachsuite', modules) ) # Don't send empty modules list - schema won't allow it - if outfitting and this.outfitting != (this.horizons, outfitting): + if outfitting and this.outfitting != (horizons, outfitting): self.send(cmdr, { '$schemaRef': f'https://eddn.edcd.io/schemas/outfitting/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -593,13 +608,13 @@ Msg:\n{msg}''' ('systemName', entry['StarSystem']), ('stationName', entry['StationName']), ('marketId', entry['MarketID']), - ('horizons', this.horizons), + ('horizons', horizons), ('modules', outfitting), ('odyssey', entry['odyssey']) ]), }) - this.outfitting = (this.horizons, outfitting) + this.outfitting = (horizons, outfitting) def export_journal_shipyard(self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]) -> None: """ @@ -616,9 +631,10 @@ Msg:\n{msg}''' :param entry: the relevant journal entry """ ships: List[Mapping[str, Any]] = entry.get('PriceList') or [] + horizons: bool = entry.get('Horizons', False) shipyard = sorted(ship['ShipType'] for ship in ships) # Don't send empty ships list - shipyard data is only guaranteed present if user has visited the shipyard. - if shipyard and this.shipyard != (this.horizons, shipyard): + if shipyard and this.shipyard != (horizons, shipyard): self.send(cmdr, { '$schemaRef': f'https://eddn.edcd.io/schemas/shipyard/2{"/test" if is_beta else ""}', 'message': OrderedDict([ @@ -626,13 +642,13 @@ Msg:\n{msg}''' ('systemName', entry['StarSystem']), ('stationName', entry['StationName']), ('marketId', entry['MarketID']), - ('horizons', this.horizons), + ('horizons', horizons), ('ships', shipyard), ('odyssey', entry['odyssey']) ]), }) - # this.shipyard = (this.horizons, shipyard) + # this.shipyard = (horizons, shipyard) def export_journal_entry(self, cmdr: str, entry: Mapping[str, Any], msg: Mapping[str, Any]) -> None: """ From d8689e5b9b1f81178706777d4faa354b310629b2 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 12:10:09 +0100 Subject: [PATCH 301/316] Revert "EDDN: If `this.horizons` is true, use that in `is_horizons()` check" This reverts commit ca80edd34d88298a2c9d933f79f0da8e68d870eb. --- plugins/eddn.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 751e1085..6768d023 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1441,22 +1441,6 @@ def is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY :param ships: Ships available at the docked station. :return: bool - True if the Cmdr has Horizons access. """ - # First check the Journal-sourced flag - # NB: This assumes game currently running, rather than "old - # journal file". `LoadGame` determines monitor.cmdr, which determines - # the account used for CAPI, so it *should* match. - # - # For this to be wrong, the user would have had to have, e.g.: - # - # 1. Logged into Cmdr in Horizons/Odyssey, then back out again. - # 2. Logged into Cmdr in base game, either on another machine, or - # have removed the Journal file after. - # 3. Re-run EDMC and triggered a manual CAPI update. - # - # This seems unlikely. - if this.horizons: - return True - economies_colony = False modules_horizons = False ship_horizons = False From 694321064d439a983ba08087b0b78d7951943686 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 12:20:06 +0100 Subject: [PATCH 302/316] EDDN: Rename is_horizons() and document that it's for CAPI data only OK, it has `data` passed in, so this should be obvious, but let's make it explicit both by name and in the docstring. The docstring now also emphasies that *this* check **MUST** be used for CAPI data, as it's dependent only on the availability of Horizons on the account, and not on the `LoadGame` flags. --- plugins/eddn.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 6768d023..31ab721c 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -452,7 +452,7 @@ Msg:\n{msg}''' # Horizons flag - will hit at least Int_PlanetApproachSuite other than at engineer bases ("Colony"), # prison or rescue Megaships, or under Pirate Attack etc - horizons: bool = is_horizons( + horizons: bool = capi_is_horizons( data['lastStarport'].get('economies', {}), modules, ships @@ -500,7 +500,7 @@ Msg:\n{msg}''' """ modules, ships = self.safe_modules_and_ships(data) - horizons: bool = is_horizons( + horizons: bool = capi_is_horizons( data['lastStarport'].get('economies', {}), modules, ships @@ -1432,10 +1432,19 @@ def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: # noqa: CCR001 MAP_STR_ANY = Mapping[str, Any] -def is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY) -> bool: +def capi_is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY) -> bool: """ Indicate if the supplied data indicates a player has Horizons access. + This is to be used **only** for CAPI-sourced data and **MUST** be used + for CAPI data!!! + + If the account has Horizons access then CAPI `/shipyard` will always see + the Horizons-only modules/ships. You can**NOT** use the Journal horizons + flag for this! If logged in to the base game on an account with Horizons, + which is all of them now, CAPI `/shipyard` will *still* return all of the + Horizons-only modules and ships. + :param economies: Economies of where the Cmdr is docked. :param modules: Modules available at the docked station. :param ships: Ships available at the docked station. From c97b539ac3a1e96bb3a7591aa02f8a484e51e88a Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 12:25:35 +0100 Subject: [PATCH 303/316] EDDN: Revert to using capi_is_horizons() in export_commodities() --- plugins/eddn.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 31ab721c..71a93112 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -342,6 +342,12 @@ Msg:\n{msg}''' :param data: a dict containing the starport data :param is_beta: whether or not we're currently in beta mode """ + modules, ships = self.safe_modules_and_ships(data) + horizons: bool = capi_is_horizons( + data['lastStarport'].get('economies', {}), + modules, + ships + ) commodities: List[OrderedDictT[str, Any]] = [] for commodity in data['lastStarport'].get('commodities') or []: # Check 'marketable' and 'not prohibited' @@ -376,7 +382,7 @@ Msg:\n{msg}''' ('stationName', data['lastStarport']['name']), ('marketId', data['lastStarport']['id']), ('commodities', commodities), - ('horizons', this.horizons), + ('horizons', horizons), ('odyssey', this.odyssey), ]) From cf5806021ec8ccbcf2d9274797b9dd8ee5c343ba Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 12:33:11 +0100 Subject: [PATCH 304/316] PLUGINS.md: capi_is_horizons() now --- PLUGINS.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index e8a27474..8a27efff 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -659,13 +659,12 @@ New in version 5.0.1: `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`, -but blindly retrieves the value rather than having a strict default. There's +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 -[plugins/eddn.py:is_horizons()](./plugins/eddn.py). That function is necessary -because CAPI data doesn't (didn't always?) have an indication of Horizons or -not. +[plugins/eddn.py:capi_is_horizons()](./plugins/eddn.py). That function is +necessary because CAPI data doesn't have a simple indication of Horizons-ness. New in version 5.0.3: From 7dcb275e9b390108ca2aa038430fe96cb9b21759 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 14:18:11 +0100 Subject: [PATCH 305/316] EDDN: Document HORIZONS_SKU, and to NOT use the others --- plugins/eddn.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 71a93112..7e72aa0f 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -91,7 +91,15 @@ class This: this = This() -HORIZ_SKU = 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' +# This SKU is tagged on any module or ship that you must have Horizons for. +HORIZONS_SKU = 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' +# ELITE_HORIZONS_V_COBRA_MK_IV_1000` is for the Cobra Mk IV, but +# is also available in the base game, if you have entitlement. +# `ELITE_HORIZONS_V_GUARDIAN_FSDBOOSTER` is for the Guardian FSD Boosters, +# which you need Horizons in order to unlock, but could be on sale even in the +# base game due to entitlement. +# Thus do **NOT** use either of these in addition to the PLANETARY_LANDINGS +# one. # TODO: a good few of these methods are static or could be classmethods. they should be created as such. @@ -465,8 +473,8 @@ Msg:\n{msg}''' ) to_search: Iterator[Mapping[str, Any]] = filter( - lambda m: self.MODULE_RE.search(m['name']) and m.get('sku') in (None, HORIZ_SKU) and - m['name'] != 'Int_PlanetApproachSuite', + lambda m: self.MODULE_RE.search(m['name']) and m.get('sku') in (None, HORIZONS_SKU) + and m['name'] != 'Int_PlanetApproachSuite', # noqa: E131 modules.values() ) @@ -1467,7 +1475,7 @@ def capi_is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_ST logger.error(f'economies type is {type(economies)}') if isinstance(modules, dict): - modules_horizons = any(module.get('sku') == HORIZ_SKU for module in modules.values()) + modules_horizons = any(module.get('sku') == HORIZONS_SKU for module in modules.values()) else: logger.error(f'modules type is {type(modules)}') @@ -1475,7 +1483,7 @@ def capi_is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_ST if isinstance(ships, dict): if ships.get('shipyard_list') is not None: if isinstance(ships.get('shipyard_list'), dict): - ship_horizons = any(ship.get('sku') == HORIZ_SKU for ship in ships['shipyard_list'].values()) + ship_horizons = any(ship.get('sku') == HORIZONS_SKU for ship in ships['shipyard_list'].values()) else: logger.debug('ships["shipyard_list"] is not dict - FC or Damaged Station?') From 9a58da57cd1186fa717924f56419bf70f42c4f2f Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 18 Oct 2021 15:39:18 +0100 Subject: [PATCH 306/316] Pre-Release 5.2.0-beta1: appversion & changelog --- ChangeLog.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ config.py | 2 +- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 2eb45d0b..ee586eed 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,88 @@ produce the Windows executables and installer. --- +Pre-Release 5.2.0 beta1 +=== + +* Now built using Python 3.9.7. + +* `Update` button is disabled if CQC/Arena is detected. + +* Frontier CAPI queries now run in their own thread. There should be no + change in functionality for users. This affects both EDMarketConnector + (GUI) and EDMC (command-line). + +* `File` > `Status` will now use cached CAPI data, rather than causing a fresh + query. **Currently if data has not yet been cached nothing will happen when + trying to use this**. + +* `File` > `Save Raw Data` also now uses the cached CAPI data, rather than + causing a fresh query. This will write an empty JSON `{}` if no data is + yet available. + +* New [docs/Licenses/](docs/Licenses/) directory containing all relevant + third-party licenses for the software this application uses. + +* `Settings` > `Output` > `File Location` 'Browse' button will now always be + available, even if no output options are active. + +* The 'no git installed' logging when running from source is now at INFO + level, not ERROR. This will look less scary. + +* EDMarketConnetor command-line arguments have been re-ordered into + logical groups for `--help` output. + +* Support added for several new EDDN schemas relating to specific Journal + events. Until the live EDDN server is updated (planned for 2021-10-21) you + will see minor error logging when this fails due to 'unknown schema'. + + Schema support added for: + - `codexentry/1` + - `fssdiscoveryscan/1` + - `navbeaconscan/1` + - `navroute/1` + - `scanbarycentre/1` + +Bug Fixes +--- + +* A bug preventing `--force-localserver-auth` from working has been fixed. + +* `horizons` and `odyssey` flags should now always be set properly on *all* + EDDN messages. The `horizons` flag was missing from some. + +Developers +--- + +* New `journal_entry_cqc()` function for plugins to receive journal events + *specifically and **only** when the player is in CQC/Arena*. This allows + for tracking things that happen in CQC/Arena without polluting + `journal_entry()`. See [PLUGINS.md](PLUGINS.md) for details. + +* Command-line argument `--trace-all` to force all possible `--trace-on` to be + active. + +* Contributing.md has been updated for how to properly use `trace_on()`. + +* EDMC.(py,exe) now also makes use of `--trace-on`. + +* EDMarketConnector now has `--capi-pretend-down` to act as if the CAPI + server is down. + +* Killswitches now have support for removing key/values entirely, or forcing + the value. See [docs/Killswitches.md](docs/Killswitches.md) for details. + +* `state['Odyssey']` added, set from `LoadGame` journal event. + +* You can now test against a different EDDN server using `--eddn-url` + command-line argument. This needs to be the *full* 'upload' URL, i.e. for + the live instance this is `https://eddn.edcd.io:4430/upload/`. + +* New command-line argument `--eddn-tracking-ui` to track the EDDN handling + code's idea of the current Journal and Status.json Body(Name|ID). + +--- + Release 5.1.3 === diff --git a/config.py b/config.py index 05cacd89..77ee6860 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ appcmdname = 'EDMC' # <https://semver.org/#semantic-versioning-specification-semver> # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.1.4-beta0' +_static_appversion = '5.2.0-beta1' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' From 3ec9c81e192df088726266162846f44e5f92d9b0 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Tue, 19 Oct 2021 16:01:54 +0100 Subject: [PATCH 307/316] EDDN: Drop any 'unknown schema' messages so they're not retried --- plugins/eddn.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 7e72aa0f..b4cd508d 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -298,7 +298,10 @@ Msg:\n{msg}''' if unknown_schema := self.UNKNOWN_SCHEMA_RE.match(e.response.text): logger.debug(f"EDDN doesn't (yet?) know about schema: {unknown_schema['schema_name']}" f"/{unknown_schema['schema_version']}") - # return # Pretend it went OK so this message isn't retried + # NB: This dropping is to cater for the time when EDDN + # doesn't *yet* support a new schema. + self.replaylog.pop(0) # Drop the message + self.flush() # Truncates the file, then writes the extant data else: status['text'] = self.http_error_to_log(e) From 832316517e6b80145d102e91a8d5c3a426a19fa3 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 20 Oct 2021 09:14:43 +0100 Subject: [PATCH 308/316] requirements-dev: Bump flake8-use-fstring and flake8 itself --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 152c5507..5af19055 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ wheel # Static analysis tools -flake8==3.9.2 +flake8==4.0.1 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 flake8-comprehensions==3.7.0 @@ -13,7 +13,7 @@ flake8-isort==4.1.1 flake8-json==21.7.0 flake8-noqa==1.2.0 flake8-polyfill==1.0.2 -flake8-use-fstring==1.1 +flake8-use-fstring==1.2 mypy==0.910 pep8-naming==0.12.1 From 3e046c1f65bc367e607c4b1fb77bd00a31e65394 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 20 Oct 2021 13:14:16 +0100 Subject: [PATCH 309/316] Pre-Release 5.2.0-beta1: appversion and changelog --- ChangeLog.md | 7 +++++++ config.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index ee586eed..fc4f79b4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,13 @@ produce the Windows executables and installer. --- +Pre-Release 5.2.0 beta2 +=== + +* Don't save, for later retry, any EDDN messages that are met with 'unknown + schema' from the server. This should alleviate any "laggy UI" problems that + their accumulation can cause. + Pre-Release 5.2.0 beta1 === diff --git a/config.py b/config.py index 77ee6860..b6cdf0d9 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ appcmdname = 'EDMC' # <https://semver.org/#semantic-versioning-specification-semver> # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.2.0-beta1' +_static_appversion = '5.2.0-beta2' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' From 531302b7da41f7e769a17b14ddde1303ece20644 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 20 Oct 2021 14:32:04 +0100 Subject: [PATCH 310/316] Notify user when 'File' > 'Status' fails Rather than a silent failure, set a status line message and also play the 'bad' sound to draw their attention to it. --- EDMarketConnector.py | 1 + L10n/en.template | 313 +++++++++++++++++++++++-------------------- stats.py | 7 + 3 files changed, 175 insertions(+), 146 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4c480f47..897ec378 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1167,6 +1167,7 @@ class AppWindow(object): return except companion.ServerConnectionError: + # LANG: Frontier CAPI server error when fetching data self.status['text'] = _('Frontier CAPI server error') except companion.CredentialsError: diff --git a/L10n/en.template b/L10n/en.template index 2221e523..2851c474 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -1,175 +1,190 @@ /* Language name */ "!Language" = "English"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:212; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Error: Frontier CAPI didn't respond"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:231; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "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:247; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Docked but unknown station: EDO Settlement?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:257; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Error: Invalid Credentials"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:273; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Error: Wrong Cmdr"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:397; companion.py:482; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Error"; -/* 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:440; companion.py:445; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Error: Couldn't check token customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:451; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Error: customer_id doesn't match!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:473; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Error: unable to get token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:774; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI down for maintenance"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Frontier CAPI query failure"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:786; */ -"Frontier CAPI server error" = "Frontier CAPI server error"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:485; EDMarketConnector.py:780; EDMarketConnector.py:1440; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Update"; -/* 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:568; prefs.py:866; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Always on top"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:698; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Unknown"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:767; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "Error: Check E:D journal file location" = "Error: Check E:D journal file location"; -/* 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:774; edsm.py:257; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:776; EDMarketConnector.py:1199; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Role"; -/* 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:776; EDMarketConnector.py:1209; EDMarketConnector.py:1232; stats.py:365; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Ship"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:777; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Suit"; -/* 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:778; prefs.py:606; stats.py:367; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "System"; -/* 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:779; prefs.py:624; prefs.py:761; stats.py:368; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Station"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:782; EDMarketConnector.py:797; EDMarketConnector.py:800; EDMarketConnector.py:1917; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "File"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:783; EDMarketConnector.py:798; EDMarketConnector.py:801; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Edit"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:784; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "View"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:785; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Window"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:786; EDMarketConnector.py:799; EDMarketConnector.py:802; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Help"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:789; EDMarketConnector.py:815; EDMarketConnector.py:1486; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "About {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:791; EDMarketConnector.py:814; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Check for Updates..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:792; EDMarketConnector.py:806; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Save Raw Data..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:793; EDMarketConnector.py:805; stats.py:362; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:794; EDMarketConnector.py:812; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Privacy Policy"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:795; EDMarketConnector.py:813; EDMarketConnector.py:1520; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Release Notes"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:807; EDMarketConnector.py:1918; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Settings"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:808; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Exit"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:811; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Documentation"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:818; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Copy"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:824; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Logging in..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:840; EDMarketConnector.py:1351; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Authentication successful"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:871; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "You're not docked at a station!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:879; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Station doesn't have anything!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:884; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Station doesn't have a market!"; -/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:913; */ -"CQC detected, aborting CAPI query" = "CQC detected, aborting CAPI query"; +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "CAPI query aborted: Cmdr name unknown"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:934; stats.py:280; */ +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "CAPI query aborted: Game mode unknown"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"CAPI query aborted: Current system unknown" = "CAPI query aborted: Current system unknown"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:963; */ +"CAPI query aborted: In other-ship multi-crew" = "CAPI query aborted: In other-ship multi-crew"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "CAPI query aborted: CQC (Arena) detected"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Fetching data..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:973; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: No commander data returned"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:977; stats.py:299; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Who are you?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:983; stats.py:309; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Where are you?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:990; stats.py:314; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "What are you flying?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1145; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Frontier CAPI server error"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Last updated at %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1174; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1175; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1176; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1434; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "cooldown {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1546; prefs.py:305; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1611; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Shutting down..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1906:1912; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1916; prefs.py:976; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1927; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins Without Python 3.x Support"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -217,31 +232,31 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDB Journal processing disabled. See Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:225; eddn.py:630; eddn.py:982; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Sending data to EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:274; eddn.py:920; eddn.py:955; eddn.py:994; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Error: Can't connect to EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:292; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Error: EDMC is too old for EDDN. Please update."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:298; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Error: Validation Failed (EDMC Too Old?). See Log"; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:303; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "EDDN Error: Returned {STATUS} status code"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Send station data to the Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:721; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Send system and scan data to the Elite Dangerous Data Network"; -/* 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:732; */ +/* 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:1133; */ "Delay sending until docked" = "Delay sending until docked"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:796; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "EDDN journal handler disabled. See Log."; /* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ @@ -256,7 +271,7 @@ /* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Key"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "None"; /* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ @@ -418,230 +433,236 @@ /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Disabled Plugins"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Balance"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Loan"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federation"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:369; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"Status: Don't yet know your Commander name" = "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:288; */ +"Status: No CAPI data yet" = "Status: No CAPI data yet"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Value"; -/* stats.py: Status dialog title; In files: stats.py:378; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Ships"; diff --git a/stats.py b/stats.py index 89e79d9a..334e8358 100644 --- a/stats.py +++ b/stats.py @@ -11,6 +11,7 @@ import companion import EDMCLogging import myNotebook as nb # noqa: N813 from edmc_data import ship_name_map +from hotkey import hotkeymgr from l10n import Locale from monitor import monitor @@ -274,11 +275,17 @@ class StatsDialog(): def showstats(self) -> None: """Show the status window for the current cmdr.""" if not monitor.cmdr: + hotkeymgr.play_bad() + # LANG: Current commander unknown when trying to use 'File' > 'Status' + self.status['text'] = _("Status: Don't yet know your Commander name") return # TODO: This needs to use cached data if companion.session.FRONTIER_CAPI_PATH_PROFILE not in companion.session.capi_raw_data: logger.info('No cached data, aborting...') + hotkeymgr.play_bad() + # LANG: No Frontier CAPI data yet when trying to use 'File' > 'Status' + self.status['text'] = _("Status: No CAPI data yet") return capi_data = json.loads( From 3f0f1fe39f1ea611e43c56f0888a4099d250e317 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 20 Oct 2021 14:50:03 +0100 Subject: [PATCH 311/316] CAPI: Avoid '... EDO Settlement?' for 'Update' in space 1. Logged out in space 2. Logged back in 3. Run EDMC 4. Hit Update button, get "Docked but unknown station: EDO Settlement?" In investigating this I realised that the entire else needed to be "if docked according to CAPI". Then it became clear that there was a redundant conditional, as the enclosing one is already testing: `capi_response.capi_data['lastStarport']['name'] != monitor.station:` Making the: `if last_station != monitor.station` conditional un-necessary. --- EDMarketConnector.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4c480f47..c0614bdc 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1072,24 +1072,13 @@ class AppWindow(object): f"{monitor.state['OnFoot']!r} and {monitor.station!r}") raise companion.ServerLagging() - else: - last_station = None - if capi_response.capi_data['commander']['docked']: - last_station = capi_response.capi_data['lastStarport']['name'] - - if monitor.station is None: - # Likely (re-)Embarked on ship docked at an EDO settlement. - # Both Disembark and Embark have `"Onstation": false` in Journal. - # So there's nothing to tell us which settlement we're (still, - # or now, if we came here in Apex and then recalled ship) docked at. - logger.debug("monitor.station is None - so EDO settlement?") - raise companion.NoMonitorStation() - - if last_station != monitor.station: - # CAPI lastStarport must match - logger.warning(f"({capi_response.capi_data['lastStarport']['name']!r} != {monitor.station!r})" - f" AND {last_station!r} != {monitor.station!r}") - raise companion.ServerLagging() + elif capi_response.capi_data['commander']['docked'] and monitor.station is None: + # Likely (re-)Embarked on ship docked at an EDO settlement. + # Both Disembark and Embark have `"Onstation": false` in Journal. + # So there's nothing to tell us which settlement we're (still, + # or now, if we came here in Apex and then recalled ship) docked at. + logger.debug("docked AND monitor.station is None - so EDO settlement?") + raise companion.NoMonitorStation() self.capi_query_holdoff_time = capi_response.query_time + companion.capi_query_cooldown From 00e46679d0e7fb39f0b688a7eda546eb143c3bd9 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Wed, 20 Oct 2021 15:11:59 +0100 Subject: [PATCH 312/316] Pre-Release 5.2.0-beta3: appversion & changelog --- ChangeLog.md | 11 +++++++++++ config.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index fc4f79b4..ab487120 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,17 @@ produce the Windows executables and installer. Pre-Release 5.2.0 beta2 === +* Trying to use `File` > `Status` when the current commander is unknown, or + there is has been no CAPI data retrieval yet, will now result in the 'bad' + sound being played and an appropriate status line message. + +* Pressing the 'Update' button when in space (not docked, not on a body + surface) will no longer cause a spurious "Docked but unknown station: EDO + Settlement?" message. + +Pre-Release 5.2.0 beta2 +=== + * Don't save, for later retry, any EDDN messages that are met with 'unknown schema' from the server. This should alleviate any "laggy UI" problems that their accumulation can cause. diff --git a/config.py b/config.py index b6cdf0d9..93078020 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ appcmdname = 'EDMC' # <https://semver.org/#semantic-versioning-specification-semver> # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.2.0-beta2' +_static_appversion = '5.2.0-beta3' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' From 7e1c1be08bf2cd79000c295b7f56791c151f38a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 17:03:58 +0000 Subject: [PATCH 313/316] build(deps-dev): bump flake8-use-fstring from 1.2 to 1.2.1 Bumps [flake8-use-fstring](https://github.com/MichaelKim0407/flake8-use-fstring) from 1.2 to 1.2.1. - [Release notes](https://github.com/MichaelKim0407/flake8-use-fstring/releases) - [Commits](https://github.com/MichaelKim0407/flake8-use-fstring/compare/v1.2...v1.2.1) --- updated-dependencies: - dependency-name: flake8-use-fstring dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5af19055..1814aec0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,7 +13,7 @@ flake8-isort==4.1.1 flake8-json==21.7.0 flake8-noqa==1.2.0 flake8-polyfill==1.0.2 -flake8-use-fstring==1.2 +flake8-use-fstring==1.2.1 mypy==0.910 pep8-naming==0.12.1 From a622d1673a6cb3e9fd317b71ec6f2e9d2e3eb837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Oct 2021 17:02:29 +0000 Subject: [PATCH 314/316] build(deps-dev): bump flake8-use-fstring from 1.2.1 to 1.3 Bumps [flake8-use-fstring](https://github.com/MichaelKim0407/flake8-use-fstring) from 1.2.1 to 1.3. - [Release notes](https://github.com/MichaelKim0407/flake8-use-fstring/releases) - [Commits](https://github.com/MichaelKim0407/flake8-use-fstring/compare/v1.2.1...v1.3) --- updated-dependencies: - dependency-name: flake8-use-fstring dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 1814aec0..16d01f5a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -13,7 +13,7 @@ flake8-isort==4.1.1 flake8-json==21.7.0 flake8-noqa==1.2.0 flake8-polyfill==1.0.2 -flake8-use-fstring==1.2.1 +flake8-use-fstring==1.3 mypy==0.910 pep8-naming==0.12.1 From 13d0f94a467b64b66eb4e9c0a75204bd1f567105 Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 25 Oct 2021 10:54:57 +0100 Subject: [PATCH 315/316] L10n: Translations updated --- L10n/cs.strings | 399 +++++++++++++++++------------------ L10n/de.strings | 444 ++++++++++++++++++++------------------- L10n/es.strings | 411 ++++++++++++++++++------------------ L10n/fi.strings | 399 +++++++++++++++++------------------ L10n/fr.strings | 352 +++++++++++++++---------------- L10n/hu.strings | 342 +++++++++++++++--------------- L10n/it.strings | 387 +++++++++++++++++----------------- L10n/ja.strings | 444 ++++++++++++++++++++------------------- L10n/ko.strings | 452 +++++++++++++++++++++------------------- L10n/lv.strings | 322 ++++++++++++++-------------- L10n/nl.strings | 352 +++++++++++++++---------------- L10n/pl.strings | 444 ++++++++++++++++++++------------------- L10n/pt-BR.strings | 444 ++++++++++++++++++++------------------- L10n/pt-PT.strings | 444 ++++++++++++++++++++------------------- L10n/ru.strings | 444 ++++++++++++++++++++------------------- L10n/sl.strings | 287 ++++++++++++------------- L10n/sr-Latn-BA.strings | 420 +++++++++++++++++++------------------ L10n/sr-Latn.strings | 444 ++++++++++++++++++++------------------- L10n/sv-SE.strings | 347 +++++++++++++++--------------- L10n/uk.strings | 383 +++++++++++++++++----------------- L10n/zh-Hans.strings | 422 +++++++++++++++++++------------------ 21 files changed, 4241 insertions(+), 4142 deletions(-) diff --git a/L10n/cs.strings b/L10n/cs.strings index abc52781..5dc62b4a 100644 --- a/L10n/cs.strings +++ b/L10n/cs.strings @@ -1,172 +1,166 @@ /* Language name */ "!Language" = "Čeština"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Chyba: Frontier CAPI neodpovídá"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Chyba: Server Frontieru nejede"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Chyba: Server Frontieru laguje"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Chyba: Chybné přihlašovací údaje"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Chyba: Nesprávný CMDR"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Chyba"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Chyba: Nelze se připojit k Inaře"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Chyba: Nejde ověřit token customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Chyba: customer_id nesouhlasí!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Chyba: nepovedlo se získat token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Dotaz na Frontier CAPI selhal"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Chyba Frontier CAPI serveru"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Aktualizovat"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Vždy navrchu"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Neznámý"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Role"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Loď"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Oblek"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Systém"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Stanice"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Soubor"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Upravit"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Zobrazit"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Okno"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Nápověda"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "O {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Zkontrolovat aktualizace..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Uložit surová data..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Zásady ochrany osobních údajů"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Poznámky k verzi"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Nastavení"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Ukončit"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentace"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopírovat"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Přihlašování..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Přihlášení bylo úspěšné"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Nejste zadokováni ve stanici!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Stanice nenabízí žádné služby!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Stanice nemá obchod!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Načítání dat..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Nebyla vrácena žádná data komandéra"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Kdo jste?"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Kde zrovna jste?"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "S jakou lodí létáte?"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Chyba Frontier CAPI serveru"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Poslední aktualizace %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Počkej ještě {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Vypínání..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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." = "Jeden nebo více aktivních pluginů ještě stále nepodporuje Python 3.x. Podívejte se na jejich seznam v záložce '{PLUGINS}' v '{FILE}' > '{SETTINGS}'. Zkontrolujte si jestli není dostupná novější verze pluginu, pokud ne, tak kontaktujte vývojáře pluginu o tom, že musí upravit jejich plugin pro Python 3.x.\n\nPlugin můžete vypnout přidáním '{DISABLED}' na konec názvu jeho složky."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Pluginy"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Pluginy, které nepodporují Python 3.x"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -181,7 +175,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignorovat"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Výchozí"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -214,416 +208,413 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Zpracování EDDB deníku je deaktivováno. Zkontrolujte log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Odesílání dat do EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Chyba: Nelze se připojit k EDDN"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Odeslat data o stanici do Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Odeslat sken a data o systému do Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Pozdržet odeslání po zadokování"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "Obsluha EDDN deníku je deaktivována. Zkontrolujte log."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Odesílat letový log a status CMDRa do EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Přihlašovací údaje Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Jméno CMDRa"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API klíč"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Žádná"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "Obsluha EDSM deaktivována. Zkontrolujte log."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Chyba: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Chyba: Nelze se připojit k EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Odesílat letový log a status CMDRa na Inaru"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Přihlašovací údaje k Inaře"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara deaktivována. Zkontrolujte log."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Chyba: Server Frontieru SKU problém"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Chyba: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Nastavení"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Vyberte, která data chcete ukládat"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Obchodní data ve formátu CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Obchodní data ve formátu Trade Dangerous"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Konfigurace lodi"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automaticky aktualizovat při zadokování"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Cesta k souboru"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "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:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Procházet..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Výstup"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "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:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Klávesová zkratka"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Klávesová zkratka"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "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:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Pouze pokud je Elite: Dangerous aktivní"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Přehrát zvuk"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Vypnout automatickou kontrolu aktualizací aplikace, pokud jste ve hře"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Preferované stránky"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Shipyard"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Použít alternativní URL metodu"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Míra logování"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Nastavení"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Soukromí"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normální text"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Zvýrazněný text"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Jazyk"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Schéma"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Tmavé"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Průhledné"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Procenta UI škálování"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 je výchozí hodnota{CR}je potřeba restartovat,{CR}aby se změna projevila!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Průhlednost hlavního okna"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 znamená plně neprůhledné.{CR}Okno se aktualizuje v reálném čase"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Vzhled"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Složka pluginů"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Otevřít"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Zapnuté pluginy"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Pluginy bez podpory Python 3.x:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informace jak migrovat pluginy."; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Vypnuté pluginy"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Zůstatek"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Půjčka"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federation"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Minimalizovat do lišty"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Poddůstojník"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Cena"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Lodě"; diff --git a/L10n/de.strings b/L10n/de.strings index 1e5936d2..83a5963f 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Prüfe"; /* Language name */ "!Language" = "Deutsch"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Fehler: Frontier CAPI antwortet nicht"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Fehler: Frontier-Server ist offline"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Fehler: Frontier-Server ist überlastet"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Angedockt aber unbekannte Station: EDO-Siedlung?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Fehler: Ungültige Anmeldedaten"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Fehler: Falscher Kdt."; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Fehler"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Fehler: Verbindung zu Inara fehlgeschlagen"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Fehler: Konnte das Token customer_id nicht prüfen"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Fehler: customer_id stimmt nicht überein!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Fehler: konnte Token nicht erhalten"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI offline wegen Wartung"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Frontier CAPI Anfrage gescheitert"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Frontier CAPI Server-Fehler"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Aktualisieren"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Immer im Vordergrund"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Unbekannt"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "Error: Check E:D journal file location" = "Fehler: Prüfe E:D Journal Dateispeicherort"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "Cmdr" = "Kdt."; -/* 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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Rolle"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Schiff"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Anzug"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "System"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Station"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Datei"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Bearbeiten"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Ansicht"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Fenster"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Hilfe"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Über {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Auf Aktualisierungen überprüfen..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Speichere Originaldaten..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Datenschutzrichtlinie"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Anmerkungen"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Einstellungen"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Beenden"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentation"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopieren"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Anmelden..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Authentifizierung erfolgreich"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Du bist an keiner Station angedockt!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Diese Station hat nichts!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Diese Station hat keinen Markt!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "CAPI Anfrage abgebrochen: Kdt. Name unbekannt"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "CAPI Anfrage abgebrochen: Spielmodus unbekannt"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"CAPI query aborted: Current system unknown" = "CAPI Anfrage abgebrochen: Aktuelles System unbekannt"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:963; */ +"CAPI query aborted: In other-ship multi-crew" = "CAPI Anfrage abgebrochen: In anderem Schiff in Multicrew"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "CAPI Anfrage abgebrochen: CQC (Arena) erkannt"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Daten werden abgerufen..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Keine Kommandanten-Daten erhalten"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Wer bist du?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Wo bist du?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Was fliegst du?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Frontier CAPI Server-Fehler"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Zuletzt aktualisiert um %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Kanonier"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Steuer"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Wartezeit {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Programm schließt..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins ohne Python 3.x Support"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignorieren"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Standard"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDB Journal-Verarbeitung deaktiviert. Siehe Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Daten werden an das EDDN gesendet..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Fehler: Keine Verbindung zum EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN-Fehler: EDMC ist zu alt für EDDN. Bitte aktualisieren."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN-Fehler: Validierung fehlgeschlagen (EDMC zu alt?). Siehe Log"; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "EDDN-Fehler: {STATUS} Statuscode zurückgegeben"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Schicke Stationsdaten an das Elite Dangerous Data Netzwerk"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Sende System- und Scandaten zum Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Senden verzögern bis angedockt"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "EDDN Journal-Handler deaktiviert. Siehe Log."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Sende Fluglog und CMDR-Status an EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite: Dangerous Star Map Anmeldedaten"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Kommandantenname"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Schlüssel"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Keine Zuweisung"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "EDSM-Handler deaktiviert. Siehe Log."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Fehler: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Fehler: Kann keine Verbindung zu EDSM aufbauen"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Sende Fluglog und CMDR-Status an Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara-Anmeldedaten"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara deaktiviert. Siehe Log."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Fehler: Frontier-Server SKU Problem"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Fehler: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Einstellungen"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Bitte wähle zu speichernde Daten aus"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Marktdaten als CSV Datei"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Marktdaten als Trade-Dangerous Datei"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Schiffsausstattung"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automatisch beim Andocken aktualisieren"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Speicherort"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Ordner..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Durchsuchen..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Ausgabe"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D Journal Dateispeicherort"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Makro"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Öffne Systemeinstellungen"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Nur wenn Elite: Dangerous geöffnet ist"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Ton abspielen"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Während das Spiel läuft nicht auf Updates überprüfen"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Favorisierte Webseiten"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Werft"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Alternative URL-Methode benutzen"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Log-Level"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfiguration"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Haupt-UI-Datenschutzoptionen"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Private Gruppe-Name im UI verstecken"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Multi-Crew Kapitänsnamen verstecken"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Datenschutz"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normaler Text"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Hervorgehobener Text"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Sprache"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Theme"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Dunkel"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparent"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "UI-Skalierung in Prozent"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 bedeutet Standardwert{CR}Neustart erforderlich, um{CR}Änderungen zu übernehmen!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Durchsichtigkeit Hauptfenster"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 bedeutet komplett undurchsichtig.{CR}Fenster wird in Echtzeit aktualisiert"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Aussehen"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Plugin-Ordner"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Öffnen"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Aktivierte Plugins"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Plugins ohne Python 3.x Support"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informationen zum Migrieren von Plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Deaktivierte Plugins"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Kontostand"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Kredit"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Kampfrang"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Handelsrang"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Erkunderrang"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC-Rang"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Föderation"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Imperium"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Machtspiele"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmlos"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Zumeist Harmlos"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Neuling"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Kompetent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Experte"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Meister"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Gefährlich"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Tödlich"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Bettelarm"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Sehr Arm"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Krämer"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Kleinhändler"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Kaufmann"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Großhändler"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Unternehmer"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Magnat"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Total planlos"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "In die Taskleiste minimieren"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Fast Planlos"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Späher"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Landkundiger"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Wegebner"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pistensucher"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Aufseher"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pionier"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Hilflos"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Zumeist Hilflos"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Anfänger"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Halbprofi"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Profi"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Sieger"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Held"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Rekrut"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Kadett"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Fähnrich"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Maat"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Obermaat"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Feldwebel"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Leutnant"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Kapitänleutnant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Korvettenleutnant"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Fregattenkapitän"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Kapitän"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Konteradmiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vizeadmiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Fremder"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Leibeigener"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Knappe"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Ritter"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Fürst"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Graf"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquise"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Herzog"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prinz"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "König"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Wertung 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Wertung 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Wertung 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Wertung 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Wertung 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"Status: Don't yet know your Commander name" = "Status: Dein Kommandanten-Name ist noch nicht bekannt"; + +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:288; */ +"Status: No CAPI data yet" = "Status: Noch keine CAPI-Daten"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Wert"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Schiffe"; diff --git a/L10n/es.strings b/L10n/es.strings index e33e4c98..22ac976d 100644 --- a/L10n/es.strings +++ b/L10n/es.strings @@ -1,180 +1,172 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Comprobar"; /* Language name */ "!Language" = "Español"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Error: El CAPI de Frontier no respondió"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Error: El servidor de Frontier está caído"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Error: El servidor de Frontier está retrasado"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Atracado pero estación desconocida: ¿Asentamiento EDO?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Error: Credenciales Inválidas"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Error: Cmdt Incorrecto"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Error"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Error: No se puede conectar con Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Error: No se pudo comprobar el token customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Error: ¡customer_id no coincide!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Error: no se pudo conseguir el token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Fallo de consulta del CAPI de Frontier"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Error de servidor CAPI de Frontier"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Actualizar"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Siempre visible"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Desconocido"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "Error: Check E:D journal file location" = "Error: Comprueba la localización del fichero journal de 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "Cmdr" = "Cmdt"; -/* 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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Nave"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Traje"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Sistema"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Estación"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Archivo"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Editar"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Visualización"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Ventana"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Ayuda"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Acerca de {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Buscar Actualizaciones..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Guardar Datos Sin Procesar..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Estado"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Política de Privacidad"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Notas de la versión"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Configuración"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Salir"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Documentación"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Copiar"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Identificándose..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autenticación correcta"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "¡No estás atracado en una estación!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "¡La estación no tiene nada!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "¡La estación no tiene mercado!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Recopilando datos..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: No se devolvió datos de comandante"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "¿Quién eres?"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "¿Dónde estás?"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "¿Qué estás volando?"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Error de servidor CAPI de Frontier"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Última actualización: %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Caza"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Artillero"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Timón"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Espere {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "Aceptar"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Apagando..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Uno o más de tus plugins habilitados aún no tienen soporte para Python 3.x. Por favor, consulta la lista en la pestaña '{PLUGINS}' de '{FILE}' > '{SETTINGS}'. Deberías comprobar si hay una versión actualizada disponible, si no, avisa al desarrollador de que necesita actualizar el código para Python 3.x.\n\nPuedes deshabilitar un plugin cambiando el nombre de su carpeta para que tenga '{DISABLED}' al final del nombre."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins Sin Soporte para Python 3.x"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +181,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignorar"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Por defecto"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +214,422 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Procesamiento de Journal para EDDB desactivado. Ver Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Enviando datos a EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Error: No se puede conectar a EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "Error EDDN: EDMC es demasiado antiguo para EDDN. Actualiza por favor."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "Error EDDN: Validación Fallida (¿EDMC Demasiado Antiguo?). Revisa el Log"; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "Error EDDN: Devolvió código de estado {STATUS}"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Enviar datos de la estación a Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Enviar datos de sistema y escáner a Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Retrasar envío hasta el atraque"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "Manejo de Journal para EDDN desactivado. Ver Log."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Enviar Registro de Vuelo y estado del CMDT a EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Credenciales de Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Nombre de Comandante"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "Llave API"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Ninguna"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "Manejo de Journal para EDSM desactivado. Ver Log."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Error: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Error: No se puede conectar a EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Enviar Registro de Vuelo y estado del CMDT a Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Credenciales de Inara"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara desactivado. Ver Log."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Error: Problema de SKU en el servidor de Frontier"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Error: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Preferencias"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Seleccione qué datos quiere guardar"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Datos de mercado en formato CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Datos de mercado en formato Trade Dangerous"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Equipamiento de la Nave"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Actualizar automáticamente al atracar"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Localización de archivos"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Cambiar..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Examinar..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Salida"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "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:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Tecla de acceso directo"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Tecla de acceso directo"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Abrir Preferencias del Sistema"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Solo cuando Elite: Dangerous es la aplicación activa"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Reproducir sonido"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Desactivar Comprobación Automática de Actualizaciones de la Aplicación mientras juega"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Sitios Web preferidos"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Astillero"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Usar método URL alternativo"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Nivel de registro"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Configuración"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privacidad"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Texto normal"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Texto resaltado"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Idioma"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Oscuro"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparente"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Porcentaje escala UI"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 significa Por Defecto{CR}Se requiere reiniciar{CR}para que los cambios surtan efecto!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Transparencia de la ventana principal"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 significa completamente opaco.{CR}La ventana se actualiza en tiempo real."; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Apariencia"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Carpeta de Plugins"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Abrir"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Plugins Activados"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Plugins sin soporte para Python 3.x:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Información sobre migración de plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Plugins Desactivados"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Saldo"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Préstamo"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combate"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Comercio"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Exploración"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federación"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Imperio"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Inofensivo"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Casi inofensivo"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novato"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competente"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Experto"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Maestro"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Peligroso"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Letal"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Élite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Pobre"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Casi pobre"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Buhonero"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Distribuidor"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Mercader"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Bróker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Empresario"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Magnate"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Perdido"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Minimizar a la bandeja de sistema"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Casi perdido"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Explorador"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Topógrafo"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Rastreador"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Guía"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Descubridor"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pionero"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Indefenso"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Casi indefenso"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Aficionado"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semiprofessional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Profesional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Campeón"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Héroe"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiador"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recluta"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadete"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Guardiamarina"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Sargento"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Sargento primero"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Subteniente"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Alférez de fragata"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Teniente de navío"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Capitán de corbeta"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Capitán de fragata"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Capitán de navío"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Contraalmirante"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vicealmirante"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Almirante"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Forastero"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Siervo"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Escudero"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Caballero"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Señor"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Barón"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Vizconde"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Conde"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Conde Palatino"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marqués"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duque"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Príncipe"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Rey"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Nivel 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Nivel 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Nivel 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Nivel 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Nivel 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Valor"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Naves"; diff --git a/L10n/fi.strings b/L10n/fi.strings index bb260466..7a463158 100644 --- a/L10n/fi.strings +++ b/L10n/fi.strings @@ -1,172 +1,166 @@ /* Language name */ "!Language" = "Suomi"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Virhe: Frontier CAPI ei vastaa."; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Virhe: Frontierin palvelin ei vastaa"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Virhe: Frontierin palvelin vastaa vanhoilla tiedoilla"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Virhe: kirjautuminen ei kelpaa"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Virhe: väärä komentaja"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Virhe"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Virhe: Ei yhteyttä Inaraan"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Virhe: Ei kyetty tarkastamaan polettia customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Virhe: customer_id ei löydy vastaavuutta!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Virhe: ei kyetty saamaan polettia"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Frontier CAPI kyselyvirhe."; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Frontier CAPI palvelinvirhe."; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Päivitä"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Aina päällimmäisenä"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Tuntematon"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Rooli"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Alus"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Asuste"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Järjestelmä"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Asema"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Tiedosto"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Muokkaa"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Näytä"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Ikkuna"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Apua"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Ohjelmasta {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Tarkista päivitykset..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Tallenna raakadata..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Tila"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Tietosuojakäytäntö"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Julkaisutiedot"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Valinnat"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Lopeta"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentaatio"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopioi"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Kirjaudutaan..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Todennus onnistui"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Et ole telakoituneena asemalle!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Asemalla ei ole mitään!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Asemalla ei ole kaupankäyntiä!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Noudetaan tietoja..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: tietoja ei löytynyt"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Kuka olet?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Missä olet?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Mitä alusta lennät?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Frontier CAPI palvelinvirhe."; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Päivitetty viimeksi %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Hävittäjä"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Aseiden käyttäjä"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Pilotti"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Jäähtymisaika {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Sulkeutuu..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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." = "Yksi tai useampi käyttämistäsi lisäosista ei sisällä vielä Pyhton 3.x -tukea. Tarkista tiedoston '{FILE}' > '{SETTINGS}' välilehti '{PLUGINS}' lisäosista, onko niistä uudempaa versiota saatavilla; muussa tapauksessa ota yhteyttä sen kehittäjään ja kerro, että sen lähdekoodi on päivitettävä Python 3.x -tasolle.\n\nVoit estää lisäosan toiminnan nimeämällä sen hakemiston siten, että se päättyy tekstiin '{DISABLED}'."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Lisäosat"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Lisäosat ilman Python 3.x -tukea"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -181,422 +175,419 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Älä huomioi"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Oletus"; /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDB lokikirjan käsittely ei käytössä. Katso lokitiedot."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Lähetetään tietoja EDDN:ään..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Virhe: ei saatu yhteyttä EDDN:ään"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Lähetä asematiedot Elite Dangerous Data Network -palveluun"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Lähetä järjestelmä- ja skannaustiedot Elite Dangerous Data Network -palveluun"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Lähetä tiedot vasta telakoitumisen jälkeen"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "EDDB lokikirjan käsittelijä ei käytössä. Katso lokitiedot."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Lähetä lentoloki ja komentajatiedot EDSM:ään"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map -kirjautuminen"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Komentajanimi"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API avain"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Ei määritelty"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "ESDM käsittelijä ei käytössä. Katso lokitiedot."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Virhe: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Virhe: ei saatu yhteyttä EDSM:ään"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Lähetä lentoloki ja komentajatiedot Inaraan"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inaran kirjautuminen"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara ei käytössä. Katso lokitiedot."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Virhe: Frontier -palvelimen SKU-ongelma"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Inaran virhe: {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Valinnat"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Valitse mitkä tiedot tallennetaan"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Markkinadata CSV -formaatissa"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Markkinadata Trade Dangerous -formaatissa"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Aluksen konfiguraatio"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Päivitä automaattisesti telakoituessa"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Tiedostosijainti"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Muuta..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Selaa..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Tallennus"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D lokikirjan tiedostosijainti"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Näppäimistöoikopolku"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Pikanäppäin"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "{APP} tarvitsee lupasi pikakuvakkeiden käyttämiseen"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Avaa järjestelmän asetukset"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Vain jos Elite:Dangerous on käynnissä"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Toista ääni"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Estä automaattiset päivitystarkastukset pelaamisen aikana"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Ensisijaiset sivustot"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Telakka"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Käytä vaihtoehtoista URL-metodia"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Lokin syvyys"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Valinnat"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Yksityisyys"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normaali teksti"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Korostettu teksti"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Kieli"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Teema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Tumma"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Läpinäkyvä"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Käyttöliittymän skaalausprosentti"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 tarkoittaa oletusta{CR}Muutosten käyttöönotto{CR}edellyttää uudelleenkäynnistystä!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Pääikkunan läpinäkyvyys"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 tarkoittaa täysin peittävää.{CR}Ikkuna päivittyy reaaliajassa."; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Ulkoasu"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Lisäosien hakemistosijainti"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Avaa"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Käytössä olevat liitännäiset"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Lisäosat, joissa ei ole Python 3.x -tukea:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Tietoja lisäosien siirrosta"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Estetyt liitännäiset"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Saldo"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Lainaa jäljellä"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Taistelija -ranking"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Kaupankäynti -ranking"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Tutkimusmatkailija -ranking"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC -ranking"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federaation sotilasarvosi"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Imperiumin sotilasarvosi"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Valtapeli -ranking"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Pienennä tehtäväpalkkiin"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Luokitus 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Luokitus 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Luokitus 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Luokitus 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Luokitus 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Arvo"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Alukset"; diff --git a/L10n/fr.strings b/L10n/fr.strings index 5f83a098..c6fbee13 100644 --- a/L10n/fr.strings +++ b/L10n/fr.strings @@ -1,524 +1,518 @@ /* Language name */ "!Language" = "Français"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Erreur : Le serveur Frontier est indisponible"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Erreur : Le serveur Frontier ne répond pas"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Erreur : Identifiants invalides"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Erreur : Cmdr incorrect"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Erreur : Connexion impossible à Inara"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Toujours visible"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "Cmdr" = "Cmd"; -/* 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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Rôle"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Vaisseau"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Combinaison"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Système"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Station"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Fichier"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Édition"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Présentation"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Fenêtre"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Aide"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "À propos de {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Chercher une mise à jour..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Sauvegarder les Données Brutes..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "État"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Politique de Confidentialité"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Notes de publication"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Paramètres"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Quitter"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Documentation"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Copier"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Connexion en cours..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Authentification réussie"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Vous n'êtes pas amarré à une station !"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "La station n'a rien du tout !"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "La station n'a pas de marché !"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Récupération des données..."; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Qui êtes-vous ?"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Où êtes-vous ?"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Avec quoi volez-vous ?"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Dernière mise à jour à %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Chasseur"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Artilleur"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Pilote"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Temps de recharge {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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." = "Un ou plusieurs de vos plugins activés ne sont pas encore compatibles avec Python 3.x. Regardez la liste sur l'onglet '{PLUGINS}' de '{FILE}' > '{SETTINGS}'. Vous devriez vérifier s'il y a une version mise à jour, sinon alertez le développeur qu'il doit mettre à jour le code pour Python 3.x.\n\nVous pouvez désactiver un plugin en renommant son dossier pour qu'il ait '{DISABLED}' à la fin du nom."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins non compatibles avec Python 3.x"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Par défaut"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Envoi des données à EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Erreur : Connexion impossible à EDDN"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Envoyer les données de la station à Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Envoyer les systèmes et les informations de scan à Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Retarder l'envoi jusqu'à l'amarrage"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Envoyer le journal de vol et le statut Cmdr à EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Informations d'identification pour Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Nom du commandant"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "Clé API"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Aucun"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Erreur : EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Erreur : Connexion impossible à EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Envoyer le journal de vol et le statut Cmdr à Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Informations d'identification pour Inara"; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Erreur : Le serveur Frontier a un problème de SKU"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Erreur : Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Préférences"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Veuillez choisir les données à sauvegarder"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Données du marché au format CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Données du marché au format Trade Dangerous"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Équipements du vaisseau"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Mettre à jour automatiquement à l'amarrage"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Emplacement des fichiers"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "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:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Parcourir..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Sorties"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "Emplacement du journal E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Raccourci clavier"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Raccourci clavier"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "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:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Seulement quand Elite: Dangerous est l'application active"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Jouer un son"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Désactiver la vérification automatique des mises à jour quand vous êtes en jeu"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Sites préférés"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Chantier naval"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Configuration"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Confidentialité"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Texte normal"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Texte en surbrillance"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Langue"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Thème"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Foncé"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparent"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Apparence"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Répertoire des plugins"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Ouvrir"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Plugins activés"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informations sur la migration des plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Plugins désactivés"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Solde"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Emprunt"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combattant"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Commerçant"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorateur"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Fédération"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Inoffensif"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Bleu"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Compétent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Vilain"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Vétéran"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Létal"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Élite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Sans-le-sou"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mendiant"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Boutiquier"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Revendeur"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Marchand"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Courtier"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Magnat"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Vagabond"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Touriste"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Voyageur"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Éclaireur"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Prospecteur"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Découvreur"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Navigateur"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pionnier"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recrue"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Matelot"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Second Maître"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Premier Maître"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Major"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Enseigne"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Capitaine de Corvette"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Capitaine de Frégate"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Capitaine de Vaisseau"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Contre-Amiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice-Amiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Amiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Étranger"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Écuyer"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Chevalier"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Banneret"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Vicomte"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Comte"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Duc"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Archiduc"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Roi"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rang 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rang 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rang 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rang 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rang 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Valeur"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Vaisseaux"; diff --git a/L10n/hu.strings b/L10n/hu.strings index 0df65833..34055ac0 100644 --- a/L10n/hu.strings +++ b/L10n/hu.strings @@ -1,509 +1,503 @@ /* Language name */ "!Language" = "Magyar"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Hiba:Frontier szerver nem elérhető"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Hiba:Frontier szerver laggol"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Hiba:Érvénytelen hitelesítő adatok"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Hiba:Rossz parancsnok"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Hiba:Nem lehet kapcsolódni az Inarához"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Frissítés"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Mindig felül"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "Cmdr" = "Parancsnok"; -/* 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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Szerep"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Hajó"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Rendszer"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Állomás"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Fájl"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Szerkeszt"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Nézet"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Ablak"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Segítség"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Az {APP} névjegye"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Frissítések keresése"; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Nyers adat elmentése"; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Státusz"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Adatvédelmi irányelvek"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Kiadási megjegyzések"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Beállítások"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Kilépés"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentáció"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Másol"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Bejelentkezés"; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "A hitelesítés sikerült"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = " Nem dokkoltál az állomáson"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Az állomáson nincs semmi!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Nincs piac az állomáson!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Adatok lekérése"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Ki vagy?"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Hol vagy?"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Mivel repülsz?"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Utoljára frissítve %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Vadászgép"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Lövész"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Kormányos"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Lehűl {SS} másodperc"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "Ok"; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Pluginok"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Alap"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Adatok küldése az EDDN-nek..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Hiba:Nem lehet kapcsolódni az EDDN-hez"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Állomás adatok küldése az EDDN-nek"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Rendszer és szken adatok küldése az EDDN-nek"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Küldés késletetése amig dokkolsz"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Repülési napló és Parancsnok státusz küldése az EDSM-nek"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Csillag térkép hitelesítése"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Parancsnok név"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API kulcs"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Egyik sem"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Hiba:EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Hiba:nem lehet kapcsólodni az EDSM-hez"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Repülési napló és Parancsnok státusz küldése az Inarának"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara hitelesitő adatok"; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Hiba:Frontier szerver SKU probléma"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Hiba:Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Beállítások"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Kérem válassza ki a menteni kivánt adatokat."; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Piaci adat CSV fájlban"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Piaci adat Trade Dangerous fájlban"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Hajó felszerelése"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automatikusan frissit dokkolásnál"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Fálj helye"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "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:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Böngész"; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Kimenet"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D journal fájl helye"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Billentyűparancsok"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Gyorsgomb"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "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:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Csak ha az Elite Dangerous fut"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Zene be"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Preferált weboldalak"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Hajógyár"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfiguráció"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Titkosítás"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normál txt"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Kiemelt szöveg"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Nyelv"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Téma"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Sötét"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Átlárható"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Megjelenés"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Plugin mappa"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Nyisd ki"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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}'"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Pluginok engedélyezése"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Plugin kikapcsolása"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Egyenleg"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Kölcsön"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Harc"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Kereskedés"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Felfedező"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Föderáció"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Birodalom"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "ártalmatlan"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Többnyire ártalmatlan"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Kezdő"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Képesített"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Szakértő"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Mester"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Veszélyes"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Halálos"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elit"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Nincstelen"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Többnyire nincstelen"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Házaló ügynők"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Kereskedő"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Kereskedő"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Bróker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Vállalkozó"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Iparmágnás"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Céltalan"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Tøbnyire céltalan"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Felderítő"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Megfigyelő"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Útkereső"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Útkereső"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Vándor"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Utász"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "tehetetlen"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Többnyire tehetetlen"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amatőr"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Fél profi"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Szakember"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Bajnok"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hős"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiátor"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Újonc"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Kadét"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Tengerészkadét"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Tizedes"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Törzs zászlós"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Tiszthelyettes"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Zászlós"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Hadnagy"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Főhadnagy"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Ezredes"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Ellentengernagy"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "FőEllentengernagy"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Altengernagy"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admirális"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Kivűlálló"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Jobbágy"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Földesúr"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Lovag"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Báró"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Vicomte"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Számol"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Gróf"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Őrgróf"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Herceg"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Herceg"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Király"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Osztákgozás 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Osztályozás 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Osztályozás 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Osztályozás 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Osztályozás 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Érték"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Hajók"; diff --git a/L10n/it.strings b/L10n/it.strings index c68d4d82..928e5b89 100644 --- a/L10n/it.strings +++ b/L10n/it.strings @@ -1,159 +1,151 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Controlla"; /* Language name */ "!Language" = "Italiano"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Errore: Il Server Frontier è offline"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Errore: il server Frontier sta laggando"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Errore: Credenziali non valide"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Errore: Cmdr sbagliato"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Errore"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Errore: Non riesco a connettermi a Inara"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Errore nel server Frontier CAPI"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Recupera"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Sempre in primo piano"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Sconosciuto"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Ruolo"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Nave"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Tuta"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Sistema"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Stazione"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "File"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Appunti"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Vista"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Finestra"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Guida"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Informazioni su {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Controlla aggiornamenti..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Salva i Dati Grezzi..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Informativa sulla privacy"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Note di rilascio"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Impostazioni"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Esci"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Documentazione"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Copia"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Mi sto loggando..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autenticazione riuscita"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Non sei parcheggiato in nessuna stazione !"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "La Stazione non ha niente !"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "La Stazione non ha un Market !"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Sto raccogliendo i dati..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Nessun dato sul commandante"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Chi sei ?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Dove sei finito ?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Stai volando ?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Errore nel server Frontier CAPI"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Ultimo aggiornamento alle %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Cacciatore"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Artigliere"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Timoniere"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "disponibile tra {SS} secondi"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Spegnimento..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Uno o più dei tuoi plugin non ha ancora il supporto per Python 3.x. Per favore guarda la lista '{PLUGINS}' nella tab a '{FILE}' > '{SETTINGS}'. Dovresti controllare se ci sono aggiornamenti, altrimenti avvisa gli sviluppatori che devono aggiornare il codice a Python 3.x.\n\nPuoi disabilitare il plugin rinominando la cartella aggiungendo '{DISABLED}' alla fine del nome."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins senza Supporto Python 3.x"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -168,7 +160,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignora"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Predefinito"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -195,410 +187,407 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Elaborazione di EDDB Journal disabilitata. Guarda il Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Sto inviando i dati a EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Errore: Non riesco a connettermi a EDDN"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Manda i dati della stazione a Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Manda il sistema e i dati scansionati a EDDN"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Ritarda l'invio fino a che si è ormeggiati"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Manda il flight log e lo stato Cmdr a EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Credenziali per Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Nome Commandante"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Key"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Nessuna"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Errore: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Errore: Non riesco a connettermi a EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Manda il flight log e lo stato Cmdr a Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Credenziali per Inara"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara disabilitato. Vedi il Log."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Errore: il server Frontier ha un problema SKU"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Errore: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Preferenze"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Si prega di scegliere che dati salvare"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Market data in formato CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Market data in formato Trade Dangerous"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Equipaggiamento della nave"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Auto-aggiornamento all'ormeggio"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Percorso del file"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Cambia..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Sfoglia..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Output"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "locazione del file E:D journal"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Scorciatoia di tastiera"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Apri Preferenze di Sistema"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Solo quando “Elite: Dangerous” è in primo piano"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Notifica sonora"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Disabilita gli aggiornamenti automatici quando sei in-game"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Sito web preferito"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Spazioporto"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Usare metodo URL alternativo"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Livello di Log"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Configurazione"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Dati personali"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Testo normale"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Testo evidenziato"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Lingua"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Scuro"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Trasparente"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Percentuale zoom della UI"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 significa Default{CR}Riavvio Richiesto per{CR}applicare i cambiamenti!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Trasparenza della finestra principale"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 stà per completamente opaco.{CR}Finestra è aggiornata in tempo reale"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Aspetto"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Cartella dei plugins"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Apri"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Plugin abilitati"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Plugin senza il supporto a Python 3.x :"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informazioni sulla migrazione dei plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Plugin disabilitati"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Saldo"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Prestito"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federazione"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Impero"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Riduci a icona nel Vassoio di sistema"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Valore"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Navi"; diff --git a/L10n/ja.strings b/L10n/ja.strings index 2309d7aa..9fce4e1f 100644 --- a/L10n/ja.strings +++ b/L10n/ja.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "確認"; /* Language name */ "!Language" = "日本語"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "エラー: Frontier CAPIからの応答がありません"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "エラー: Frontier社のサーバがダウンしています"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "エラー: Frontier社のサーバが混雑しています"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "不明なステーションに着艦: EDOの基地?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "エラー: 認証情報に誤りがあります"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "エラー: 不適切なCmdr"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "エラー"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "エラー: Inaraに接続できません"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "エラー: customer_idトークンを確認できませんでした"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "エラー: customer_idがマッチしません!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "エラー: トークンが取得できません"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "Frontier CAPIがメンテナンスのため停止中"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Frontier CAPIクエリ失敗"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Frontier CAPIサーバエラー"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "更新"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "常に最前面に表示する"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "不明"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "役割"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "船"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "スーツ"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "星系"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "ステーション"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "ファイル"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "編集"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "表示"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "ウィンドウ"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "ヘルプ"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "{APP} について"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "更新の確認..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "生データの保存..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "ステータス"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "プライバシーポリシー"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "リリースノート"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "設定"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "終了"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "ドキュメント"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "コピー"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "ログイン中..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "認証成功"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "ステーションにドッキングしていません!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "このステーションには何も商品がありません!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "このステーションには市場がありません!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "CAPIクエリ中止: CMDR名が不明"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "CAPIクエリ中止: ゲームモードが不明"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"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:963; */ +"CAPI query aborted: In other-ship multi-crew" = "CAPIクエリ中止: 他の船でマルチクルー中"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "CAPIクエリ中止: CQC(Arena)が検出"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "データを取得中..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: コマンダーのデータが返ってきませんでした"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "どなたですか?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "どこにいますか?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "どの船で飛行していますか?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Frontier CAPIサーバエラー"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "最終更新時間 %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "次回更新まであと {SS}秒"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "了解"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "終了します..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "プラグイン"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: プラグインがPython 3.xをサポートしていません"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "無視"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "デフォルト"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDBジャーナル処理が無効です。ログを確認してください。"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "EDDNにデータ送信中..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "エラー: EDDNに接続できません"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "EDDNエラー: EDDNで使用するにはEDMCのバージョンが古すぎます。更新してください。"; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "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:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "EDDNエラー: ステータスコード {STATUS} が返されました"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Elite Dangerous Data Networkにステーションデータを送信する"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Elite Dangerous Data Networkに星系やスキャンした星のデータを送信する"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "ドッキングするまでデータを送信しない"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "EDDNジャーナルハンドラーが無効です。ログを確認してください。"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "フライトログとCMDRステータスをEDSMへ送信する"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Mapの認証情報"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "コマンダー名"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "APIキー"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "設定なし"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "EDSMハンドラーが無効です。ログを確認してください。"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "エラー: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "エラー: EDSMに接続できません"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "フライトログとCMDRステータスをInaraへ送信する"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara認証情報"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inaraが無効です。ログを確認してください。"; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "エラー: Frontier社のサーバで非ゲーム所有者と判断されました"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "エラー: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "環境設定"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "保存するデータの種類を選択して下さい"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "市場データをCSV形式で記録する"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "市場データをTrade Dangerous形式で記録する"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "船の装備構成"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "ドッキングした際に自動でデータを更新する"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "ファイルの出力先"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "変更..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "参照..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "出力"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:Dゲームクライアントのジャーナルファイル出力先"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "ショートカット"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "ホットキー"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "Re-start {APP} to use shortcuts" = "ショートカットを利用するには {APP} を再起動してください"; -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "{APP} がショートカットを利用するには許可が必要です"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "システム環境設定を開く"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Elite: Dangerousがアクティブの時だけ有効"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "音を鳴らす"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "ゲーム中の自動アプリケーション更新チェックを無効にする"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "お気に入りのウェブサイト"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "造船所"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "別のURL方式を使用する"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "ログレベル"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "環境設定"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "メインUIプライバシーオプション"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "UIでプライベートグループ名を表示しない"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "マルチクルー船長名を表示しない"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "プライバシー"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "通常表示"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "強調表示"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "言語"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "テーマ"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "ダーク"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "透明"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "UIスケール比率"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100はデフォルトを意味します{CR}変更した値を反映するには{CR}再起動が必要です!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "メインウィンドウの透明度"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100は完全に不透明であることを意味します。{CR}ウィンドウはリアルタイムで更新されます"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "表示"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "プラグインフォルダ"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "開く"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "ヒント: プラグインを無効にするには{CR}フォルダ名に '{EXT}' を追加してください"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "有効なプラグイン"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Python 3.xサポートのないプラグイン:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "プラグインの移行に関する情報"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "無効なプラグイン"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "口座残高"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "負債"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "戦闘ランク"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "交易ランク"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "探索ランク"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQCランク"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "連邦ランク"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "帝国ランク"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "パワープレイ"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "システムトレイに最小化する"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"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:288; */ +"Status: No CAPI data yet" = "状態: CAPIデータがありません"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "価値"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "所有船"; diff --git a/L10n/ko.strings b/L10n/ko.strings index 83d84d28..78f03306 100644 --- a/L10n/ko.strings +++ b/L10n/ko.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "확인"; /* Language name */ "!Language" = "한국어(Korean)"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "오류: Frontier CAPI가 응답이 없음"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "오류: Frontier 서버 CAPI 연결 불가"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "오류: Frontier 서버 응답이 지연되고 있음"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ -"Docked but unknown station: EDO Settlement?" = "도킹되었지만 알 수 없는 정거장임: 엘리트: 데인저러스 오디세이의 지상 기지입니까? (의역이니 확인 바람)"; +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ +"Docked but unknown station: EDO Settlement?" = "알 수 없는 정거장에 도킹함: Odyssey 지상 기지입니까?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "오류: 잘못된 자격 증명"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "오류: 잘못된 커맨더"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "오류"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "오류: Inara에 연결할 수 없음"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "오류: customer_id 토큰을 확인할 수 없었음"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "오류: customer_id가 일치하지 않습니다!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "오류: 토큰을 받지 못함"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI 점검으로 접속 불가"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Frontier CAPI 쿼리 실패"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Frontier CAPI 서버 에러"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "새로 고침"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "항상 위에 보이기"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "알 수 없음"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ -"Error: Check E:D journal file location" = "에러: 엘리트:데인저러스 journal file 위치를 확인해 주십시요"; +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ +"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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Role"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "함선"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "슈트"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "성계"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "스테이션"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "파일"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "편집"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "보기"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "창"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "도움말"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "{APP} 정보"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "업데이트 확인하기..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Raw 데이터 저장하기..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "상태"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "개인정보처리방침"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "릴리즈 노트"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "설정"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "종료"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "문서 자료"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "복사"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "로그인하는 중..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "인증 성공"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "스테이션에 도킹되어 있지 않습니다!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "스테이션에 아무것도 없습니다!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "스테이션에 마켓이 없습니다!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "CAPI 쿼리 중단: 커맨더 이름 알 수 없음"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "CAPI 쿼리 중단: 게임 모드 알 수 없음"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"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:963; */ +"CAPI query aborted: In other-ship multi-crew" = "CAPI 쿼리 중단: 다른 함선의 멀티크루로 참여중"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "CAPI 쿼리 중단: CQC(아레나) 감지됨"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "데이터 가져오는 중..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: 커맨더 정보가 반환되지 않음"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "당신은 누구시죠?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "어디 계신거죠?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "지금 어떤 함선을 몰고 계신 건가요?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Frontier CAPI 서버 오류"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "마지막 업데이트: %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "쿨다운 {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "종료하는 중..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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}' 에서 '{PLUGINS}' 탭에 들어가 활성화되어있는 플러그인 목록을 확인하십시오. 플러그인들 중 새 버전이 나온 것이 있는지도 확인해보시고, 만약 3.x를 지원하지도, 새 버전도 없다면 해당 플러그인의 개발자에게 코드를 수정하기를 문의하십시오.\n\n플러그인을 비활성화하려면 '{DISABLED}'를 폴더명 뒤에 추가하면 됩니다."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "플러그인"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Python 3.x을 지원하지 않는 플러그인"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "무시하기"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "기본값"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDB 저널 처리 비활성화됨. 로그 참고 바람."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "EDDN에 정보 전송하는 중..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "오류: EDDN에 연결할 수 없음"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ -"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN 오류: EDMC 가 구버전 입니다. 업데이트 해주세요"; +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ +"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN 오류: EDDN에 접속하기에 EDMC의 버전이 너무 오래되었습니다. 업데이트하시기 바랍니다."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ -"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN 오류: 검증 실패 (EDMC 가 구버전입니까?). 로그 참고 바람"; +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ +"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:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "EDDN 오류: 상태 코드 {STATUS}가 반환됨"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Elite Dangerous Data Network에 스테이션 정보 전송하기"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Elite Dangerous Data Network에 시스템 및 스캔 정보 전송하기"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "도킹시까지 서버에 정보 전송을 미루기"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "EDDN 저널 처리 비활성화됨. 로그 참고 바람."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "EDSM에 flight 로그와 커맨더 상태 정보 전송하기"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map 계정"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "커맨더 이름"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API 키"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "없음"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "EDSM Handler 비활성화됨. 로그 참고 바람."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "오류: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "오류: EDSM에 연결할 수 없음"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Inara에 flight 로그와 커맨더 상태 정보 전송하기"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara 계정 정보"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara 비활성화됨. 로그 참고 바람."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "오류: Frontier 서버 SKU 문제 발생"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "오류: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "설정"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "어느 데이터를 저장할 지 선택해주세요"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "CSV 형식 파일의 마켓 정보"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Trade Dangerous 형식 파일의 마켓 정보"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "함선 피팅"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "도킹시 자동으로 서버에 정보 업데이트하기"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "파일 위치"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "바꾸기..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "찾아보기..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "출력"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D 저널(journal) 파일 위치"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "단축키 설정"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "단축키"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "Re-start {APP} to use shortcuts" = "단축키를 사용하려면 {APP}을(를) 다시 시작하십시오"; -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "단축기 사용을 위해선 {APP}이 권한 허용을 필요로 합니다"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "시스템 설정 열기"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Elite: Dangerous가 활성 창 상태일때만"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "소리 재생하기"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "게임이 실행중일 때 프로그램 업데이트 확인하지 않기"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "선호하는 사이트"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "쉽야드"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "다른 URL 형식 사용하기"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "로깅 수준"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "환경 설정"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "메인 UI 개인정보 설정"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "UI에서 프라이빗 그룹 이름 숨기가"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "멀티크루 함장 이름 숨기기"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "개인정보"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "일반 텍스트"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "강조된 텍스트"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "언어"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "테마"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "어둡게"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "투명하게"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "UI 스케일 백분율"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100은 '기본 설정'을 의미하며{CR}변경사항 적용을 위해{CR}재시작이 요구됩니다!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "메인 창 투명도"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100은 완전히 불투명함을 의미합니다.{CR}창은 실시간으로 업데이트됨"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "스타일"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "플러그인 폴더"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "열기"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "팁: 플러그인을 비활성화하려면{CR} '{EXT}'를 폴더명 뒤에 추가하면 됨"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "활성 플러그인"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Python 3.x을 지원하지 않는 플러그인:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "플러그인 위치 변경에 대한 정보"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "비활성 플러그인"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "잔고"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "대출"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federation"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "시스템 트레이로 최소화"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"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:288; */ +"Status: No CAPI data yet" = "상태: 아직 CAPI 정보 없음"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "가치"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "함선(들)"; diff --git a/L10n/lv.strings b/L10n/lv.strings index 788bb2be..2d32d179 100644 --- a/L10n/lv.strings +++ b/L10n/lv.strings @@ -1,482 +1,482 @@ /* Language name */ "!Language" = "Latviešu"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Kļūda: Frontier serveris nav aktīvs"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Kļūda: Frontier serveris aizkavējas"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Kļūda: Nederīga lietotāja informācija"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Atjaunināt"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Vienmēr virspusē"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Loma"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Kuģis"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Sistēma"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Stacija"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Fails"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Labot"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Skats"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Logs"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Palīdzība"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Par {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Pārbaudīt atjauninājumus..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Saglabāt sākotnējos datus..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Statuss"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Privātuma politika"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Informācija par laidienu"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Iestatījumi"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Iziet"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentācija"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopēt"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Autorizējas..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autentifikācija sekmīga"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Jūs neesat stacijā!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Stacijā nekā nav!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Stacijā nav tirgus!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Iegūst datus..."; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Kas tu esi?"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Kur tu esi?"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Ar ko tu lido?"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Pēdējo reizi atjaunināts %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Cīnītājs"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Ložmetējnieks"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Stūre"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Atskaite {SS}"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "Labi"; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Spraudņi"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Noklusējuma"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Sūta datus uz EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Kļūda: Nevar savienoties ar EDDN"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Sūtīt stacijas datus uz Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Sūtīt stacijas datus uz Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Aizkavēt sūtīšanu līdz nosēšanai"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous zvaigžņu kartes informācija"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Komandiera vārds"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API atslēga"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Neviens"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Kļūda: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Kļūda: Nevar savienoties ar EDSM"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Iestatījumi"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Lūdzu izvēlaties kādus datus saglabāt"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Tirgus dati CSV formātā"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Tirgus dati Trade Dangerous formātā"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Kuģa loadout"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automtiski atjaunoties nosēžoties."; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Faila atrašanās vieta"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "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:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Pārlūkot..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Izvade"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "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:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Ātrais taustiņš"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Ātrais taustiņš"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "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:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Tikai kad Elite: Dangerous ir aktīvā aplikācija"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Atskaņot skaņu"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Kuģu būvētava"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfigurācija"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privātums"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normālais teksts"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Izceltais teksts"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Valoda"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Dizains"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Tumša"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Caurspīdīga"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Izskats"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Spraudņu mape"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Atvērt"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Iespējoti Spraudņi"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Atspējoti Spraudņi"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Bilance"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Aizdevums"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federation"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Nekaitīgs"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Bezpalīdzīgs"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Varonis"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiators"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Leitnants"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Bruņinieks"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Karalis"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Vērtība"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Kuģi"; diff --git a/L10n/nl.strings b/L10n/nl.strings index 0373f228..8aa3006b 100644 --- a/L10n/nl.strings +++ b/L10n/nl.strings @@ -1,524 +1,518 @@ /* Language name */ "!Language" = "Nederlands"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Fout: Frontier server is offline"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Fout: Frontier server is vertraagd"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Fout: Ongeldige inlog gegevens"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Fout: Verkeerde Cmdr"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Fout: Kan geen verbinding maken met Inara"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Altijd op voorgrond"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Schip"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Systeem"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Station"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Bestand"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Bewerken"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Beeld"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Venster"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Help"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Over {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Controleren op updates..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Sla onbewerkte gegevens op..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Privacybeleid"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Release opmerkingen"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Instellingen"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Afsluiten"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Documentatie"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopieëren"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Bezig met inloggen..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Authenticatie voltooid"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Je bent niet gedockt in een station!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Station heeft helemaal niets!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Station heeft geen markt!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Data wordt opgehaald..."; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Wie ben je?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Waar ben je?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Waar vlieg je in?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Voor het laatst bijgewerkt om %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Gevechtsvlieger"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Schutter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Stuurman"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "wachtperiode {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMS: Plugins zonder Python 3.x ondersteuning"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Standaard"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Bezig data te versturen naar EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Fout: Kan geen verbinding maken met EDDN"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Stuur station gegevens naar het Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Stuur systeem en scan gegevens naar het Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Stel verzending uit tot na het docken"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Stuur vlucht logboek en Cmdr status naar EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map inlog gegevens"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Commander naam"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Key"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Geen"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Fout: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Fout: Kan geen verbinding maken met EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Stuur vlucht logboek en Cmdr status naar Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara inlog gegevens"; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Fout: Frontier server SKU probleem"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Fout: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Preferences"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Welke gegevens wilt u opslaan?"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Markt data in CSV bestand formaat"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Markt data in Trade Dangerous bestand formaat"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Schip inrichting"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automatisch bijwerken na de landing"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Bestand locatie"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Wijzigen..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Bladeren..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Uitvoer"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D journaal bestand locatie"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Toetscombinaties"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Hotkey"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Open Systeemvoorkeuren"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Alleen wanneer Elite: Dangerous de actieve applicatie is"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Geluid afspelen"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Schakel Automatische update check uit wanneer in-game"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Voorkeur websites"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Scheepswerf"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Gebruik andere URL methode"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Configuratie"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privacy"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normale tekst"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Gemarkeerde tekst"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Taal"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Thema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Donker"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparent"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Vormgeving"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Plugins map"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Openen"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Geactiveerde plugins"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informatie over plugins migreren"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Gedeactiveerde plugins"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Balans"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Lening"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Gevechts rang"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Handels rang"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Ontdekkingsreiziger rang"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federation"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Waarde"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Schepen"; diff --git a/L10n/pl.strings b/L10n/pl.strings index 06e2eb11..9d6d9a2c 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Sprawdź"; /* Language name */ "!Language" = "Polski"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Błąd: Brak odpowiedzi od CAPI Frontier"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Błąd: Serwer Frontiera nie odpowiada"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Błąd: Serwer Frontiera laguje"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Zadokowany na nieznanej stacji: czyżby kolonia EDO?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Błąd: Dane uwierzytelniające niepoprawne"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Błąd: Niepoprawny Cmdr"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Błąd"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Błąd: Nie można połączyć się z serwisem Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Błąd: Nie można sprawdzić ID klienta z tokenu"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Błąd: ID klienta nie pasuje!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Błąd: Nie można pobrać tokenu"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "CAPI w trakcie prac serwisowych"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Błąd zapytania CAPI Frontier"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Błąd serwera CAPI Frontier"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Aktualizacja"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Zawsze na wierzchu"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Nieznany"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "Error: Check E:D journal file location" = "Błąd: Sprawdź lokalizację pliku dziennika 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Rola"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Statek"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Kombinezon"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "System"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Stacja"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Plik"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Edycja"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Widok"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Okno"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Pomoc"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "{APP}..."; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Sprawdź aktualizacje..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Zapisz Nieprzetworzone Dane..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Polityka prywatności"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Informacje o wersji"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Ustawienia"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Zakończ"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentacja"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopiuj"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Logowanie..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autoryzacja pomyślna"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Nie jesteś zadokowany do stacji!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Stacja jest pusta!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "W stacji nie ma giełdy towarowej!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "Zapytanie CAPI anulowane: nazwa komandora nieznana"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "Zapytanie CAPI anulowane: tryb gry nieznany"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"CAPI query aborted: Current system unknown" = "Zapytanie CAPI anulowane: obecny system nieznany"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:963; */ +"CAPI query aborted: In other-ship multi-crew" = "Zapytanie CAPI anulowane: jako załogant w innym statku"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "Zapytanie CAPI anulowane: wykryto arenę CQC"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Pobieranie danych..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Nie zwrócono danych dowódcy"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Kim jesteś?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Gdzie jesteś?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Czym latasz?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Błąd serwera CAPI Frontier"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Ostatnia aktualizacja %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Oczekiwanie {SS} sek."; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Wyłączanie..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Co najmniej jeden z uruchomionych pluginów nie wspiera Python 3.x. Sprawdź listę w '{FILE}' > '{SETTINGS}', sekcja '{PLUGINS}'. Upewnij się, że dostępna jest aktualna wersja pluginu, w przeciwnym razie poinformuj twórcę, że należy zaktualizować kod do wersji Python 3.x.\n\nMożesz wyłączyć plugin, dodając '{DISABLED}' na koniec nazwy jego folderu."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Pluginy"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Pluginy Nie Wspierające Python 3.x"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Zignoruj"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Domyślne"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Wyłączone przetwarzanie dziennika EDDB. Sprawdź logi."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Wysłanie danych do EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Błąd: Brak połączenia z EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "Błąd EDDN: Zbyt stara wersja EDMC. Zaktualizuj do nowszej wersji."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "Błąd EDDN: Nieudana weryfikacja (stara wersja EDDN?). Sprawdź logi."; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "Błąd EDDN: Zwrócono kod {STATUS}"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Wyślij dane do Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Wyślij dane skanowań do EDDN"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Czekaj z wysłaniem na zadokowanie"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "Wyłączona obsługa dziennika EDDB. Sprawdź logi."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Wyślij zapis lotu i status pilota do EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Dane uwierzytelniające Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Commander Name"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "Klucz API"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Brak"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "Wyłączona obsługa EDDB. Sprawdź logi."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Błąd: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Błąd: Brak połączenia z EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Wyślij zapis lotu i status pilota do Inary"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara - uprawnienia"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara jest wyłączona. Sprawdź logi."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Błąd w odpowiedzi serwera autoryzacyjnego."; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Błąd: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Preferencje"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Jakie dane zapisywać"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Dane rynkowe w formacie CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Dane rynkowe w formacie Trade Dangerous"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Wyposażenie statku"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Aktualizuj automatycznie po zadokowaniu"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Położenie pliku"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Zmień..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Przeglądaj..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Wyjście"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D Lokacja pliku dziennika"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Skrót klawiaturowy"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Skr. Klaw."; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Otwórz Preferencje systemowe"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Tylko gdy Elite: Dangerous jest aplikacją aktywną"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Odtwórz dźwięk"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Wyłącz automatyczne wyszukiwanie aktualizacji, gdy jesteś w grze"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Preferowane strony"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Stocznia"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Użyj alternatywnej metody URL"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Poziom logowania"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfiguracja"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Opcje prywatności UI"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Ukruj nazwę grupy prywatnej w UI"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Ukryj nazwę kapitana w \"multi-crew\""; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Prywatność"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Zwykły tekst"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Podświetlony tekst"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Język"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Schemat kolorystyczny"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Ciemny"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Przezroczyste"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Skalowanie UI"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 oznacza wartość domyślną{CR}Zmiany zostaną wprowadzone po{CR}ponownym uruchomieniu programu!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Przeźroczystość okna głównego"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 oznacza nieprzeźroczyste.{CR}Okno aktualizowane w czasie rzeczywistym."; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Wygląd"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Folder pluginów"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Otwórz"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Włączone pluginy"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Pluginy nie obsługiwane przez Python 3.x:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informacja o migracji pluginów"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Pluginy wyłączone"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Saldo"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Pożyczka"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federacja"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Imperium"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Zminimalizuj do zasobnika systemowego"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Ocena 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Ocena 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Ocena 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Ocena 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Ocena 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"Status: Don't yet know your Commander name" = "Status: Nieznana nazwa komandora"; + +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:288; */ +"Status: No CAPI data yet" = "Status: Brak danych CAPI"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Wartość"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Statki"; diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings index 659bce41..f7bf8702 100644 --- a/L10n/pt-BR.strings +++ b/L10n/pt-BR.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Verificar"; /* Language name */ "!Language" = "Português (Brasil)"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Erro: o CAPI da Frontier não respondeu"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Erro: O servidor da Frontier está offline"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Erro: O servidor da Frontier está com lag"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Docado, mas estação desconhecida: assentamento Odyssey?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Erro: Credenciais Inválidas"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Erro: Cmdt Errado"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Erro"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Erro: Não é possível conectar-se a Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Erro: não foi possível verificar o token customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Erro: customer_id diferente!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Erro: não foi possível obter o token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "CAPI da Frontier indisponível por manutenção"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Falha na requisição ao CAPI da Frontier"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Erro de servidor do CAPI da Frontier"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Atualizar"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Sempre no topo"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Desconhecido"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "Error: Check E:D journal file location" = "Erro: Verifique a localicação dos arquivos de Jornais 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "Cmdr" = "Cmdt"; -/* 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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Função"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Nave"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Traje"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Sistema"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Estação"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Arquivo"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Editar"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Visualizar"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Janela"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Ajuda"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Sobre {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Verificar por Atualizações..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Salvar dados brutos..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Política de Privacidade"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Notas de atualização"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Configurações"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Sair"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Documentação"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Copiar"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Efetuando login..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autenticação bem-sucedida"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Você não está docado na estação!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "A estação não tem nada!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "A estação não tem um mercado!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "Requisição CAPI cancelada: nome de CMDT desconhecido"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "Requisição CAPI cancelada: modo de jogo desconhecido"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"CAPI query aborted: Current system unknown" = "Requisição CAPI cancelada: sistema atual desconhecido"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:963; */ +"CAPI query aborted: In other-ship multi-crew" = "Requisição CAPI cancelada: em multi-crew em outra nave"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "Requisição CAPI cancelada: CQC (Arena) detectado"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Obtendo dados..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Nenhum dado de comandante retornado"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Quem é você?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Onde está você?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "No que você está voando?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Erro de servidor do CAPI da Frontier"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Última atualização %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Caça"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Artilheiro"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Comando"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "aguarde {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Encerrando..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Um ou mais dos plugins habilitados não possuem suporte ao Python 3.x. Você pode ver a lista na guia '{PLUGINS}' em '{FILE}' > '{SETTINGS}'. Você deve verificar se existe versão atualizada ou então avisar o desenvolvedor de que ele precisa atualizar o código para o Python 3.x.\n\nVocê pode desabilitar um plugin renomeando sua pasta para que seu nome termine com '{DISABLED}'."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins sem Suporte ao Python 3.x"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignorar"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Padrão"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Processamento de Jornais no EDDB desativado. Verifique o Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Enviando log para o EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Erro: Não é possível conectar-se ao EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "Erro EDDN: o EDMC é muito antigo para o EDDN. Por favor, atualize."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "Erro EDDN: Validação falhou (EDMC antigo?). Verifique o log."; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "Erro EDDN: retornou o código {STATUS}."; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Enviar log da estação para o EDDN"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Enviar sistemas e dados escaneados para o EDDN"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Atrasar o envio até estar docado"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "Manipulador de Jornais do EDDN desativado. Verique o Log."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Enviar dados de voo e status do cmdt para o EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Credenciais do Elite Dangerous Star Map (EDSM)"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Nome do Comandante"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "Chave da API"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Nenhum"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "Manipulador do EDSM desativado. Verifique o Log."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Erro: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Erro: Não é possível conectar-se ao EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Enviar dados de voo e status do cmdt para o Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Credenciais do Inara"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara desativado. Verifique o Log."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Erro: Problema de SKU no servidor da Frontier"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Erro: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Preferências"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Por favor, escolha quais dados para salvar"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Dados de mercado no arquivo de formato CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "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:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Modulação da nave"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Atualizar automaticamente na docagem"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Localização do Arquivo"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Alterar..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Procurar..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Saída"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "Localização dos arquivos de Jornais E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Atalho de teclado"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Tecla de atalho"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Abrir preferências do sistema"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Somente quando o Elite: Dangerous é o aplicativo ativo"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Tocar som"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Desativar Verificação Automática de Atualizações do EDMC enquanto jogo"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Website preferido"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Estaleiro"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Usar URL alternativa"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Nível de Log"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Configuração"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Opções de privacidade da IU"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Ocultar nome do grupo privado na IU"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Ocultar nome do capitão em multi-crew."; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privacidade"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Texto normal"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Texto destacado"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Idioma"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Escuro"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparente"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Porcentagem de escala da IU"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 significa Padrão{CR}Necessário Reiniciar para as{CR}alterações entrarem em vigor!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Transparência da janela principal"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 significa totalmente opaco.{CR}A janela é atualizada em tempo real."; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Aparência"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Pasta dos plugins"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Abrir"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Plugins Habilitados"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Plugins sem suporte a Python 3.x:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informações de migração de plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Plugins desabilitados"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Balanço"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Empréstimo"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combate"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Comércio"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Exploração"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federação"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Império"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Poderes da galáxia"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Inofensivo"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Quase Inofensivo"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novato"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competente"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Experiente"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Mestre"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Perigoso"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Letal"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Pobre"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Quase pobre"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Camelô"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Negociante"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Mercador"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Corretor"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Empresário"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Magnata"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Perdido"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Minimizar para a área de notificação"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Quase perdido"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Explorador"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Cartógrafo"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Desbravador"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Descobridor"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Patrulheiro"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneiro"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Indefeso"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Quase Indefeso"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amador"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi-profissional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Profissional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Campeão"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Herói"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiador"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruta"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Aspirante"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Cabo"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Terceiro-Sargento"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Segundo-Sargento"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Primeiro-Sargento"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Suboficial"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Tenente"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Tenente Comandante"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Capitão de Corveta"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Capitão de Fragata"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Contra-Almirante"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice-Almirante"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Almirante"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Forasteiro"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Servo"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Escudeiro"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Cavaleiro"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lorde"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Barão"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Visconde"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Conde"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Marquês"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Duque"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Arqui-Duque"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Príncipe"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Rei"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Nível 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Nível 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Nível 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Nível 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Nível 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"Status: Don't yet know your Commander name" = "Status: Nome de CMDT ainda desconhecido"; + +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:288; */ +"Status: No CAPI data yet" = "Status: ainda sem dados CAPI"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Valor"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Naves"; diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings index 8f283d90..715369ef 100644 --- a/L10n/pt-PT.strings +++ b/L10n/pt-PT.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Verificar"; /* Language name */ "!Language" = "Português (Portugal)"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Erro: CAPI da Frontier não respondeu."; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Erro: O servidor da Frontier está offline"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Erro: O servidor da Frontier está atrasado"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Atracado numa estação desconhecida. É uma colónia EDO?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Erro: Credenciais Inválidas"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Erro: Cmdt errado"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Erro"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Erro: Não é possível conectar ao Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Erro: Impossível verificar o token de customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Erro: customer_id não foi encontrado!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Erro: Não é possível obter o token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "CAPI da Frontier em manutenção."; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Falha ao consultar a CAPI da Frontier."; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Erro do servidor CAPI da Frontier."; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Actualizar"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Sempre por cima"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Desconhecido"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "Error: Check E:D journal file location" = "Erro: Verifique a localização do ficheiro do diário 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "Cmdr" = "Cmdt"; -/* 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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Função"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Nave"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Fato"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Sistema"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Estação"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Ficheiro"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Editar"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Ver"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Janela"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Ajuda"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Sobre {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Procurar por Actualizações..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Salvar dados em bruto..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Estado"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Política de Privacidade"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Notas da Versão"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Definições"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Sair"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Documentação"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Copiar"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Realizando login..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autenticação bem-sucedida"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Não está atracado numa Estação"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "A Estação não tem nada!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "A Estação não possui um mercado!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "Consulta à CAPI abortada: Nome do Comandante desconhecido"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "Consulta à CAPI abortada: Modo de Jogo desconhecido"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"CAPI query aborted: Current system unknown" = "Consulta à CAPI abortada: Sistema actual desconhecido"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:963; */ +"CAPI query aborted: In other-ship multi-crew" = "Consulta à CAPI abortada: Jogador em multi-tripulação noutra nave."; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "Consulta à CAPI abortada: CQC (Arena) detectada."; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Obtendo dados..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Dados do Comandante não recebidos"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Quem é você?"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Onde é que está?"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "No que é que está a voar?"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Erro do servidor CAPI da Frontier."; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Última actualização: %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Caça"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Artilheiro"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Comando"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "aguarde {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "A Desligar..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins sem suporte para Python 3.x"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignorar"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Predefinido"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Processamento do diário do EDDB desactivado. Verifique o ficheiro de registos."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "A enviar dados para o EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Erro: Não é possível conectar ao EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "Erro EDDN: Versão do EDMC é demasiado antiga para usar com a EDDN. Por favor actualize."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "Erro EDDN: Validação falhou (Versão do EDMC demasiado antiga?). Verifique os ficheiros de registo."; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "Erro EDDN: Recebido código de estado {STATUS}"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Enviar dados da estação para o Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Envia dados do Sistema e Scans efectuados para o Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Atrasar o envio até estar atracado"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "Gestor do diário do EDDN desactivado. Verifique o ficheiro de registos."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Enviar dados de voo e o estado do CMDT para o EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Credenciais do Elite Dangerous Star Map (EDSM)"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Nome do Comandante"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "Chave API"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Nenhum"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "Gestor do EDSM desactivado. Verifique o ficheiro de registos."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Erro: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Erro: Não é possível conectar ao EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Enviar dados de voo e o estado do CMDT para o Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Credenciais Inara"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara desactivado. Verifique o ficheiro de registos."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Erro: Problema de SKU no servidor da Frontier"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Erro: Inara: {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Preferências"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Por favor escolher quais os dados a guardar"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Ficheiro dos dados de Mercado no formato CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Ficheiro dos dados de Mercado no formato Trade Dangerous"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Módulos da nave"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Actualizar automaticamente ao atracar"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Localização do Ficheiro"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Alterar..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Procurar..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Saída"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "Localização do ficheiro do diário E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Atalho de Teclado"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Tecla Rápida"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Abrir Preferências do Sistema"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Apenas quando o Elite:Dangerous é a aplicação activa"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Reproduzir som"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Desactivar Verificação de Updates Automáticos da Aplicação enquanto joga"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Websites preferidos"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Estaleiro"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Usar método URL alternativo."; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Nível de Log"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Configuração"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Opções Privacidade UI principal"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Esconder nome do grupo privado na UI"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Esconder nome de capitão em multi-tripulação."; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privacidade"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Texto normal"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Texto destacado"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Linguagem"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Escuro"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparente"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Percentagem da escala de UI"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 significa Predefinido{CR}Requer reinício para{CR}que as mudanças façam efeito!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Transparência da janela principal"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 é completamente opaco.{CR}A janela é actualizada em tempo real."; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Aparência"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Pasta dos Plugins"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Abrir"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Plugins Activados"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Plugins sem suporte para Python 3.x"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informação para migração de plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Plugins Desactivados"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Balanço"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Empréstimo"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combate"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Comércio"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Exploração"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federação"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Império"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Jogos de Poder"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Inofensivo"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Praticamente Inofensivo"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novato"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competente"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Perito"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Mestre"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Perigoso"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Mortal"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Falido"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Praticamente Pobre"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Vendedor"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Negociante"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Mercador"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Corretor"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Empreendedor"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Magnata"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Sem Destino"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Minimizar para a barra de tarefas"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Quase sem destino"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Batedor"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Cartógrafo"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Guia"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Descobridor"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Patrulheiro"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneiro"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Indefeso"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Praticamente Indefeso"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amador"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Profissional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Profissional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Campeão"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Herói"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiador"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruta"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadete"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Aspirante"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Subsargento"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Segundo-Sargento"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Sargento-mor"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Subtenente"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Tenente"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Capitão-tenente"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Tenente de Fragata"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Capitão de Fragata"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Contra-Almirante"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice-Almirante"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Almirante"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Forasteiro"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Servo"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Escudeiro"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Cavaleiro"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lorde"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Barão"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Visconde"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Conde"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquês"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duque"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Príncipe"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Rei"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Nível 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Nível 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Nível 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Nível 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Nível 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"Status: Don't yet know your Commander name" = "Estado: Nome de Comandante ainda desconhecido."; + +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:288; */ +"Status: No CAPI data yet" = "Estado: Ainda sem dados CAPI"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Valor"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Naves"; diff --git a/L10n/ru.strings b/L10n/ru.strings index d968a85d..0b025a6a 100644 --- a/L10n/ru.strings +++ b/L10n/ru.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Проверка"; /* Language name */ "!Language" = "Русский"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Ошибка: CAPI Frontier не отвечает"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Ошибка: сервера Frontier недоступны"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Ошибка: сервера Frontier нестабильны"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Неизвестный тип станции: поселение EDO?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Ошибка: неизвестное имя пользователя или неверный пароль"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Ошибка: неверное имя командира"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Ошибка"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Ошибка: не удается подключиться к Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Ошибка: невозможно проверить токен customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Ошибка: customer_id не совпадает!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Ошибка: не удается получить токен"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI остановлен для технического обслуживания"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Неудача при запросе к Frontier CAPI"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Серверная ошибка Frontier CAPI"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Обновить данные"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Поверх остальных окон"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Неизвестно"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Роль"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Корабль"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Костюм"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Система"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Станция"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Файл"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Правка"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Вид"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Окно"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Дополнительно"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "О программе {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Проверить наличие обновлений..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Сохранить \"сырые\" данные.."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Статус"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Политика конфиденциальности"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Заметки о текущем релизе"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Настройки"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Выход"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Информация о плагине"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Копировать"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Идет подключение..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Аутентификация успешно завершена"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Ваш корабль не пристыкован к станции!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "На текущей станции никакие услуги не предоставляются!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "На текущей станции рынок недоступен!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "Запрос CAPI прерван: Имя командира неизвестно"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "Запрос CAPI прерван: Игровой режим неизвестен"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"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:963; */ +"CAPI query aborted: In other-ship multi-crew" = "Запрос CAPI прерван: В мультиэкипаже другого корабля"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "Запрос CAPI прерван: Обнаружен CQC (Арена)"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Получение данных..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Нет данных пилота"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Невозможно определить личность командира!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Невозможно определить местоположение!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Невозможно определить тип корабля!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Серверная ошибка Frontier CAPI"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Последнее обновление в %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Истребитель"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Стрелок"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Командир корабля"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "пауза {SS} сек"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Выключение..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{DISABLED}'."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Плагины"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Без поддержки Python 3.x"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Игнорировать"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "По умолчанию"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Обработка журнала EDDB отключена. См. логи."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Отправка данных на EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Ошибка: не удается подключиться к EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "Ошибка EDDN: старая версия EDMC, обновитесь до последней."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "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:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "Ошибка EDDN: Возвращён статус код {STATUS}"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Отправлять информацию о станциях на Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Отправлять данные о системе и данные сканирования в Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Отложить отправку данных до завершения стыковки"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "Обработчик журнала EDDN отключен. См. логи."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Отправлять данные журнала полёта и информацию статусе командира в EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Учётные данные Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Имя командира"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "Ключ API"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Не назначено"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "Обработчик журнала EDSM отключен. См. логи."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Ошибка: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Ошибка: не удается подключиться к EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Отправлять данные журнала полёта и информацию статусе командира в Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Учётные данные Inara"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara отключена. См. логи."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Ошибка: проблема с сервером аутентификации Frontier"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Ошибка: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Настройки"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Выберите какие данные стоит сохранять"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Данные рынков в формате CSV"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Данные рынков в формате Trade Dangerous"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Оборудование корабля"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Автоматически обновлять данные при стыковке"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Путь хранения файлов"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Обзор..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Просмотреть..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Данные"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "Расположение файла журнала E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Сочетание клавиш"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Горячая клавиша"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "Re-start {APP} to use shortcuts" = "Перезапустите {APP}, чтобы использовать сочетание клавиш"; -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "{APP} требуется разрешение на использование глобальных сочетаний клавиш"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Открыты «Настройки системы»"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Только когда окно Elite: Dangerous активно"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Звуковое оповещение"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Отключить автоматическое обновление приложения во время нахождения в игре"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Предпочитаемые сайты"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Верфи"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Альтернативный URL (адрес)"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Уровень лога"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Конфигурация"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Настройки приватности главного окна"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Скрывать название приватной группы в главном окне"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Скрывать ник капитана мультикрю сессии в главном окне"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Конфиденциальность"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Обычный текст"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Выделенный текст"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Язык"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Тема оформления"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Тёмная"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Прозрачная"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Процент масштабирования интерфейса"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 - значение по умолчанию{CR}требуется перезапуск для вступления в силу изменений{CR}!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Прозрачность главного окна"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 означает полностью непрозрачный.{CR}Окно обновляется в режиме реального времени."; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Внешний вид"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Папка плагинов"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Открыть"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Подсказка: Отключить конкретный плагин можно {CR} добавив '{EXT}' к названию папки"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Включённые плагины"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Плагины без поддержки Python 3.x"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Информация о мигрирующих плагинах"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Отключенные плагины"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Баланс"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Долг"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Боевой"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Торговый"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Исследовательский"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Федерация"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Империя"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Ранг у галактических держав"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Безвредный"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "В целом безвредный"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Новичок"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Специалист"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Эксперт"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Мастер"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Опасный"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Смертоносный"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Элита"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Нищий"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Полунищий"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Мелкий торговец"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Агент"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Коммерсант"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Брокер"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Предприниматель"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Магнат"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Бесцельный"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Сворачивать в системный трей"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Почти бесцельный"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Разведчик"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Изыскатель"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Путешественник"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Следопыт"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Скиталец"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Первооткрыватель"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Беспомощный"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Почти беспомощный"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Любитель"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Полупрофессионал"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Профессионал"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Чемпион"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Герой"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Гладиатор"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Новобранец"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Кадет"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Гардемарин"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Старшина"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Главный старшина"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Уорент-офицер"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Энсин"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Лейтенант"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Капитан-лейтенант"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Начальник гарнизона"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Командир корабля"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Контр-адмирал"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Вице-адмирал"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Адмирал"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Чужак"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Крепостной"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Оруженосец"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Рыцарь"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Лорд"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Барон"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Виконт"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Граф"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Эрл"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Маркиз"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Герцог"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Принц"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Король"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Ранг 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Ранг 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Ранг 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Ранг 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Ранг 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"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:288; */ +"Status: No CAPI data yet" = "Статус: На текущий момент данные CAPI отсутствуют"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Стоимость"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Корабли"; diff --git a/L10n/sl.strings b/L10n/sl.strings index ae05d3af..503162ef 100644 --- a/L10n/sl.strings +++ b/L10n/sl.strings @@ -1,431 +1,434 @@ /* Language name */ "!Language" = "Slovenščina"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Napaka: napačno uporabniški ime ali geslo"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Osveži"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Vedno na vrhu"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Vloga"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Ladja"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Postaja"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Datoteka"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Uredi"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Pogled"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Okno"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Pomoč"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "O {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Preveri posodobitve..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Shrani Izvirne Podatke..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Pravilnik o zasebnosti"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Opombe ob izdaji"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Nastavitve"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Izhod"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentacija"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopiraj"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Prijava v teku... "; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Preverjanje pristnosti je bilo uspešno"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Nahajš se v postaji!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Postaja nima ničesar"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Postaja nima trga"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Nalagam podatke..."; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Kdo si"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Kje se nahajaš"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "S čim letiš"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Zadnja osvežitev podatkov %H:%M:%S "; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Posodobitev v {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "Potrdi"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Privzeta"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Pošiljam podatke na EDDN"; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Napaka: povezava z EDDN ni mogoča"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Pošlji podatke postaje na EDDN"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "EDStarMap uporabniški podatki"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Commander ime"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API ključ"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Brez"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Napaka: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "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:250; */ "Preferences" = "Nastavitve"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Izberite podatke, ki jih želite shraniti"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Podatki trga v CSV formatu"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Podatki trga v Trade Dangerous formatu"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Oprema ladje"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Lokacija datoteke"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Spremeni..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Prebrskaj..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Izpis"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Bližnjica"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Bližnjica"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "{APP} potrebuje dovoljenja za uporabo bližnjic"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Odpri sistemske nastavitve"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Samo ko je Elite:Dangerous aktivna aplikacija "; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Sproži zvok"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfiguracija"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privatno"; + +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Jezik"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Temno"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Prosojno"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Videz"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Ravnotežje"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Posojilo"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trgovanje"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federacija"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Cesarstvo"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Igra moči"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Peniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly peniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Enterpreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "LIeutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Read Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serif"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Ocena 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Ocena 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Ocena 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Ocena 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Ocena 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Vrednost"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Ladje"; diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings index bfbe94b4..ec854aef 100644 --- a/L10n/sr-Latn-BA.strings +++ b/L10n/sr-Latn-BA.strings @@ -1,180 +1,172 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Provjera"; /* Language name */ "!Language" = "Srpski (Latinica, Bosna i Hercegovina)"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Greška: Frontier server je nedostupan"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Greška: Frontier server je usporen"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Pristali ste, ali stanica je nepoznata: EDO naselje?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Greška: Neispravni kredencijali"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Greška: Pogrešan Cmdr"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Greška"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Greška: Nemoguće povezivanje sa Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Greška: Nemoguća provjera tokena customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Greška: customer_id se ne poklapa!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Greška: Nemoguće dobaviti token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Frontier CAPI neuspješan upit"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Frontier CAPI serverska greška"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Osvježi"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Uvijek na vrhu"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Nepoznato"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Uloga"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Brod"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Odijelo"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Stanica"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Fajl"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Uredi"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Pogled"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Prozor"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Pomoć"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "O {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Provjeri nadogradnje..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Snimi sirove podatke..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Politika privatnosti"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Informacije o verziji"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Podešavanja"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Izlaz"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentacija"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopiraj"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Prijavljivanje..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autentifikacija uspješna"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Niste pristali na stanicu"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Stanica nema ništa!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Stanica nema market!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Preuzimanje podataka..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Nema podataka o komandantu"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Ko ste vi?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Gdje se nalazite?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "U čemu letite?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Frontier CAPI serverska greška"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Posljednji put osvježeno %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "pauza {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Zatvaranje programa..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Dodaci (plugins)"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Dodaci (plugins) bez Python 3.x podrške"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +181,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignoriši"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Standardna"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +214,431 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDB obrada journala je deaktivirana. Pogledajte Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Slanje podataka na EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Greška: Nemoguće povezivanje sa EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Greška: EDMC je prestar za EDDN. Molimo ažurirajte."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Greška: Validacija nije uspjela (EDMC prestar?). Pogledajte log."; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "EDDN Greška: Statusni kod {STATUS}"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Slanje podataka o stanici na Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Slanje podataka o sistemu i skeniranju na Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Odgodi slanje do pristajanja"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "EDDN obrada journala je deaktivirana. Pogledajte Log."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Pošalji log leta i Cmdr status na EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Kredencijali za Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Ime komandanta"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API ključ"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Nije definisano"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "EDSM obrada je deaktivirana. Pogledajte Log."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Greška: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "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:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "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:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Kredencijali za Inara"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara je deaktivirana. Pogledajte Log."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Greška: SKU problem na Frontier serveru"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Greška: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Podešavanja"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Izaberite koji podaci se snimaju"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Fajl sa market podacima u CSV formatu"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Fajl sa market podacima u Trade Dangerous formatu"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Brodska oprema"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automatsko slanje nakon pristajanja"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Lokacija fajlova"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Promijeni..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Potraži..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Izvoz"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "Lokacija E:D journal fajlova"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Prečica"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Prečica"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Otvori sistemska podešavanja"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Samo kada je Elite: Dangerous aktivna aplikacija"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Zvučni efekat"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Isključi automatsku provjeru nadogradnji za vrijeme igranja"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Preferirani web sajtovi"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Brodogradilište"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Koristi alternativni URL metod"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Log nivo"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfiguracija"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Podešavanja privatnosti glavnog prozora"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Ne prikazuj naziv privatne grupe"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Ne prikazuj ime kapetana u višečlanoj posadi"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privatnost"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normalan tekst"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Označeni tekst"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Jezik"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Tamna"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Prozirna"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Procenat UI skaliranja"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 je standardna vrijednost{CR}Potreban je restart{CR}za primjenu podešavanja!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Prozirnost glavnog prozora"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 znači potpuno neprozirno.{CR}Prozor se osvježava u realnom vremenu"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Izgled"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Folder za dodatke (plugins)"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Otvori"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Aktivirani dodaci (plugins)"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Dodaci (plugins) bez Python 3.x podrške:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informacije o migraciji dodataka (plugins)"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Deaktivirani dodaci (plugins)"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Balans"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Zajam"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Borba (Combat)"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trgovina (Trade)"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federacija (Federation)"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Imperija (Empire)"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Minimiziranje u system tray"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Vrijednost"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Brodovi"; diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings index 1e1f2b55..126b9dcf 100644 --- a/L10n/sr-Latn.strings +++ b/L10n/sr-Latn.strings @@ -1,180 +1,190 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "Provera"; /* Language name */ "!Language" = "Srpski (Latinica)"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Greška: Frontier server je nedostupan"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Greška: Frontier server je usporen"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "Dokovan ali stanica nepoznata: EDO Naselje?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Greška: Pogrešni kredencijali"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Greška: Pogrešan Cmdr"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Greška"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Greška: Nemoguće povezivanje na Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Greška: Nemoguća provera tokena customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Greška: customer_id se ne poklapa!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Greška: nemoguće dobaviti token"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:781; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI isključen radi održavanja"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "Greška pri Frontier CAPI upitu"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Greška na Frontier CAPI serveru"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Osveži"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Uvek na vrhu"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Nepoznato"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "Error: Check E:D journal file location" = "Greška: Proverite lokaciju E:D žurnal 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Uloga"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Brod"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Odelo"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Stanica"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Fajl"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Izmeni"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Pogled"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Prozor"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Pomoć"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "O {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Proveri da li postoje izmene..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Snimi sirove podatke..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Polisa o privatnosti"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Informacije o ovoj verziji"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Podešavanja"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Izlaz"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentacija"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopiraj"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Prijavljivanje..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autentifikacija uspešna"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Niste pristali na stanicu!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Stanica nema ništa!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Stanica nema market!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; In files: EDMarketConnector.py:945; */ +"CAPI query aborted: Cmdr name unknown" = "CAPI upit prekinut: Ime komandira nepoznato"; + +/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:951; */ +"CAPI query aborted: Game mode unknown" = "CAPI upit prekinut: Režim igre nepoznat"; + +/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:957; */ +"CAPI query aborted: Current system unknown" = "CAPI upit prekinut: Trenutni sistem nepoznat"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:963; */ +"CAPI query aborted: In other-ship multi-crew" = "CAPI upit prekinut: U multi-posadi drugog broda"; + +/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:969; */ +"CAPI query aborted: CQC (Arena) detected" = "CAPI upit prekinut: CQC (Arena) detektovana"; + +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Preuzimanje podataka..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Nema podataka o komandiru"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Ko ste vi?"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Gde se nalazite?"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "U čemu letite?"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Greška na Frontier CAPI serveru"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Poslednji put osveženo %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "cooldown {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Gasim..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više aktiviranih dodataka (plugins) nemaju podršku za Python 3.x. Pogledajte listu u '{PLUGINS}' tabu u '{FILE}' > '{SETTINGS}'. Proverite da li postoji nadograđena verzija ili obavesite autora da treba da promeni kod za Python 3.x.\n\nMožete deaktivirati dodatak (plugin) dodavanjem '{DISABLED}' na kraju imena njegovog foldera."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Dodaci (plugins)"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Dodaci (plugins) bez Python 3.x podrške"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +199,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ignoriši"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Podrazumevano"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +232,437 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "Obrada žurnala za EDDB je deaktivirana. Pogledajte Log."; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Slanje podataka na EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Greška: Nemoguće povezivanje na EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Greška: EDMC je prestar za EDDN. Molimo ažurirajte."; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Greška: Validacija nije uspela (EDMC prestar?). Pogledajte log."; -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "EDDN Greška: Vraćen {STATUS} statusni kod."; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Slanje podataka o stanici na Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Slanje podataka o sistemu i skeniranju na Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Odloži slanje pre pristajanja"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "Obrada žurnala za EDDN je deaktivirana. Pogledajte Log."; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Pošalji log leta i Cmdr status na EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Kredencijali za Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Ime komandira"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Ključ"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Ništa"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "EDSM obrada je deaktivirana. Pogledajte Log."; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Greška: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Greška: Nemoguće povezivanje na EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "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:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara kredencijali"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara je deaktivirana. Pogledajte Log."; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Greška: SKU problem na Frontier Serveru."; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Greška: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Podešavanja"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Izaberite koji se podaci snimaju"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Fajl sa market podacima u CSV formatu"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Fajl sa market podacima u Trade Dangerous formatu"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Oprema na brodu"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automatsko slanje prilikom pristajanja"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Lokacija fajlova"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Izmeni..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Potraži..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Izlaz"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "Lokacija E:D Journal fajlova"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Skraćenica"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Skraćenica"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Otvori sistemska podešavanja"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Samo kada je Elite: Dangerous aktivna aplikacija"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Zvučni efekat"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Isključi automatsko proveravanje izmena aplikacije za vreme igranja"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Željeni web sajtovi"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Shipyard"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Koristi alternativni URL metod"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Log Nivo"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfiguracija"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "Podešavanja privatnosti za osnovni pogled"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "Ukloni ime privatne grupe iz osnovnog pogleda"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "Ukloni ime kapetana iz višečlane posade"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Privatnost"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normalan tekst"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Označeni tekst"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Jezik"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Tamno"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparentno"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Procenat UI skaliranja"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 znači podrazumevano{CR}Restart je potreban{CR}da bi se promene primenile!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Prozirnost glavnog prozora"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 znači potpuno neprozirno.{CR}Prozor se osvežava u realnom vremenu."; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Izgled"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Folder za dodatke (plugins)"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Otvori"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Aktivirani dodaci (plugins)"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Pluginovi bez podrške za Python 3.x:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Informacije o migraciji dodataka (plugins)"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Deaktivirani dodaci (plugins)"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Balans"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Pozajmica"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Borba (Combat)"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trgovina (Trade)"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Istraživanje (Explorer)"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federacija"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Imperija"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Minimizuj u sistemsku paletu (SysTray)"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rejting 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rejting 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rejting 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rejting 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rejting 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +"Status: Don't yet know your Commander name" = "Status: Još uvek nije poznato ime vašeg komandira"; + +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:288; */ +"Status: No CAPI data yet" = "Status: Još uvek nema CAPI podataka"; + +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Vrednost"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Brodovi"; diff --git a/L10n/sv-SE.strings b/L10n/sv-SE.strings index 21233a84..272c5450 100644 --- a/L10n/sv-SE.strings +++ b/L10n/sv-SE.strings @@ -1,521 +1,512 @@ /* Language name */ "!Language" = "Svenska"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Fel: Frontier server okontaktbar"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Fel: Frontier lag på server"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Fel: ogiltligt användarnamn eller lösenord"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Fel: Fel användare. Har du flera konton eller en gammal API-nyckel från ett nollställt konto?"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Fel: Kan inte ansluta till Inara"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Uppdatera"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Alltid överst"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Roll"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Skepp"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "System"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Station"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Fil"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Ändra"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "View"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Fönster"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Hjälp"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Om {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Sök efter uppdateringar..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Lagra rådata..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Status"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Integritetspolicy"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Versionsinformation"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Inställningar"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Avsluta"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Dokumentation"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Kopiera"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Loggar in..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Autentisering lyckad"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Du är inte dockad vid en station!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "Stationen är tom!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "Stationen har ingen marknad!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Hämtar data..."; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Vem är du? (felmeddelande)"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Vart är du? (felmeddelande)"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "Okänt skepp? (felmeddelande)"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Senaste uppdatering: %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Fighter"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Gunner"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Helm"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "Vänta {SS}s"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "OK"; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "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." = "En eller flera aktiverade plugins har inte stöd för Python 3.x. Var vänlig kontrollera listan med plugins under fliken '{PLUGINS}' under '{FILE}' > '{SETTINGS}'. Kontrollera om det finns en nyare version tillgänglig eller informera utvecklaren.\n\nDu kan också avaktivera din plugin genom att ändra slutet av namnet på mappen till '{DISABLED}'"; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins utan stöd för Python 3.x"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Standard"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Skickar data till EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Fel: Kan inte ansluta till EDDN"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Skicka stationsdata till Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Skicka system och scan-data till Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Skicka bara uppdateringar till EDDN när du är dockad"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Skicka flight log-data och Cmdr-status till EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map användaruppgifter"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "CMDR namn"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Nyckel"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Ingen"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Fel: EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Fel: Kan inte ansluta till EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Skicka flight log-data och Cmdr-status till Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara autentiseringsuppgifter"; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Fel: Frontier server SKU problem (licensnyckel)"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Fel: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Inställningar"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Välj vilket data som skall lagras"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Marknadsdata i CSV-formaterad fil"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Marknadsdata i Trade Dangerous-formaterad fil"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Översikt av skepp"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Automatisk uppdatering vid dockning"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Filsökväg"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Ändra..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Bläddra..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Exportdata"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D journal-fil sökväg"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Genväg, tangentbord"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Snabbkommando"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "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:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{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:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Öppna systeminställningar"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Bara när: Elite Dangerous är det aktiva programmet"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Spela ljud"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Leta inte efter programuppdateringar när spelet körs"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Föredragna webbsidor"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Översikt av skepp"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Konfiguration"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Normal text"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Markerad text"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Språk"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Tema"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Mörk"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Transparent"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Utseende"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Mapp för plugins"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Öppna"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "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"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Aktiverade plugins"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Information om migrering av plugins"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Avaktiverade plugins"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Balance"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Lån"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Federation"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amatör"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Värde"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Skepp"; diff --git a/L10n/uk.strings b/L10n/uk.strings index 3c552667..1b4aa74e 100644 --- a/L10n/uk.strings +++ b/L10n/uk.strings @@ -1,163 +1,157 @@ /* Language name */ "!Language" = "Українська"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "Помилка, Сервер гри не доступний!"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "Помилка: З`єднання з сервером гри з затримками!"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "Помилка: Невірні облікові дані!"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "Помилка! Не той обліковий запис!"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "Помилка"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "Помилка: Немає з'єднання з Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "Помилка: Не вдалося перевірити токен customer_id"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "Error: customer_id не співпадає!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "Помилка: не вдалося отримати токен"; -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "Оновлення"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "Поверх інших вікон"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "Невідомо"; -/* 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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "Посада"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "Корабель"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "Костюм"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "Система"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "Станція"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "Файл"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "Редагувати"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "Вид"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "Вікно"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "Довідка"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "Про {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "Перевірка оновлень..."; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "Зберегти необроблені дані..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "Статус"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "Політика конфіденційності"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "Інформація про реліз"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "Налаштування"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "Вихід"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "Документація"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "Копіювати"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "Вхід в..."; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "Автентифікацію пройдено успішно"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "Ви не стиковані з станцією!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "На станції немає нічого!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "На станції немає ринку!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "Отримання даних..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI: Не отримано данних пілота"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "Хто ти?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "Де ми?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "На чому летимо?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "Останнє оновлення було %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "Винищувач"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "Стрілець"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "Керівник"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "очікування {SS} сек"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "Згода"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "Закриття..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один або кілька увімкнених плагінів ще не підтримують Python 3.x. Перегляньте список на вкладці '{PLUGINS}' в '{FILE}' > '{SETTINGS}'. Ви повинні перевірити, чи є в наявності оновлена ​​версія, інакше повідомити розробника плагіну про те, що їм потрібно оновити код для Python 3.x.\n\nВи можете відключити плагін, перейменувавши його папку додавши '{DISABLED}' в кінці назви."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "Плагіни"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Плагіни без підтримки Python 3.x!"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -172,410 +166,407 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "Ігнорувати"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "Стандартне налаштування"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "Відправка даних до EDDN..."; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "Помилка: Немає зв'язку з EDDN!"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "Надсилати дані станцій до Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "Відправляти систему і результати сканування в Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "Відкласти відправку даних до стикування"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "Відправляти дані бортового журналу до EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Дані Elite Dangerous Star Map"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "Ім`я пілота"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Ключ"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "Нічого"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "Помилка: EDSM {MSG}!"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "Помилка: Немає з`єднання з EDSM!"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "Відправляти дані бортового журналу до Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Обліковий запис Inara"; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "Помилка! Проблема SKU сервера Frontier"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "Помилка: Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "Уподобання"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "Оберіть дані які буде збережено"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "Ринкові дані у форматі CSV файлу"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "Ринкові дані у форматі Trade Dangerous файлу"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "Технічні дані корабля"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "Автоматичне оновлювання при стикуванні зі станцією"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "Розташування файлу"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "Внесення змін..."; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "Перегляд..."; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "Вивід"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "Розташування файлу-журналу E:D"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "Сполучення клавіш"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "Гаряча клавіша"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "Re-start {APP} to use shortcuts" = "Перезавантажте {APP} для використання ярликів"; -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "{APP} необхідні дозволи для використання ярликів"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "Відкрити налаштування системи"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "Тільки тоді, коли Elite: Dangerous є активним додатком"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "Грати звук"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "Вимкнути автоматичні перевірки оновлення додатку, коли в грі"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "Обрані веб-сайти"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "Корабельна верф"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "Використовувати альтернативний URL-метод"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "Рівень подробиць журналу"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "Конфігурація"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "Конфіденційність"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "Нормальний текст"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "Виділений текст"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "Мова"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "Тема"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "Темний"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "Прозора"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ "UI Scale Percentage" = "Відсоток шкали інтерфейсу користувача"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 означає, що {CR} потрібно перезапустити за замовчуванням, щоб зміни вступили в силу {CR}!"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "Прозорість головного вікна"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 означає повністю непрозоре. {CR} Вікно буде оновлюватись в режимі реального часу"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "Зовнішній вигляд"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "Сховище плагінів"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "Відкрити"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Підказка: Ви можете відключити плагін через {CR} додання '{EXT}' до назви папки"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "Увімкнені плагіни"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "Плагіни без підтримки Python 3.x:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "Інформація про міграцію плагінів"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "Вимкнені плагіни"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "Баланс"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Борг"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "Бойовий"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "Торговий"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "Дослідницький"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "Близький бій (CQC)"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "Федерація"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "Імперія"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "Змагання"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Безпечний"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Здебільшого Безпечний"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Новачок"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Компетентний"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Експерт"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Майстер"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Небезпечний"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Убивчий"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Еліта"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Злидень"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Здебільшого Нужденний"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Штовхач"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Дилер"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Торговець"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Брокер"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Підприємець"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Магнат"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Безцільний"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "Згорнути програму"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Здебільшого Безцільний"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Розвідник"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Геодезист"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Новатор"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Слідопит"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Рейнджер"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Піонер"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Безпорадний"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Здебільшого Безпомічний"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Аматор"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Напів-професійний"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Професійний"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Чемпіон"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Герой"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Гладіатор"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Рекрут"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Кадет"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Гардемарин"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Унтер Офіцер"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Старший Унтер Офіцер"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Старший сержант"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Прапорщик"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Лейтенант"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Капітан III рангу"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Капітан II рангу"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Капітан I рангу"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Контр-адмірал"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Віце-адмірал"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Адмірал"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Чужинець"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Кріпак"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Зброєносець"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Лицар"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Пан"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Барон"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Віконт"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Граф"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Дворянин"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Маркіз"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Герцог"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Принц"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "Цар"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Рейтинг 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Рейтинг 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Рейтинг 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Рейтинг 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Рейтинг 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "Вартість"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "Кораблі"; diff --git a/L10n/zh-Hans.strings b/L10n/zh-Hans.strings index e5ec2436..9d3765b8 100644 --- a/L10n/zh-Hans.strings +++ b/L10n/zh-Hans.strings @@ -1,180 +1,172 @@ -/* Generic 'Check', as in 'Check E:D journal file location' [EDMarketConnector.py] */ -"Check" = "检查"; /* Language name */ "!Language" = "简体中文"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:171; */ +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ "Error: Frontier CAPI didn't respond" = "错误:Frontier CAPI 无响应"; - -/* OLD: Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier server is down" = "错误:Frontier 服务器不可用"; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:190; */ +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ "Error: Frontier server is lagging" = "错误:Frontier 服务器繁忙"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:205; */ +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ "Docked but unknown station: EDO Settlement?" = "已靠站但站点未知:可能是 ED: Odyssey 的地面设施?"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:214; */ +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ "Error: Invalid Credentials" = "错误:无效的登录凭证"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:230; */ +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:281; */ "Error: Wrong Cmdr" = "错误:指挥官不一致"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:341; companion.py:422; */ +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:405; companion.py:490; */ "Error" = "错误"; - -/* [inara.py] */ -"Error: Can't connect to Inara" = "错误:无法连接到 Inara"; -/* 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:380; companion.py:385; */ +/* 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:448; companion.py:453; */ "Error: Couldn't check token customer_id" = "错误:无法检查 customer_id 令牌"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:391; */ +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:459; */ "Error: customer_id doesn't match!" = "错误:customer_id 不匹配!"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:413; */ +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:481; */ "Error: unable to get token" = "错误:无法获取令牌"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:575; */ +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:793; */ "Frontier CAPI query failure" = "无法访问 Frontier CAPI"; -/* companion.py: Frontier CAPI data retrieval failed with 5XX code; In files: companion.py:591; */ -"Frontier CAPI server error" = "Frontier CAPI 服务器错误"; - -/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:424; EDMarketConnector.py:718; EDMarketConnector.py:1302; */ +/* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:517; EDMarketConnector.py:812; EDMarketConnector.py:1504; */ "Update" = "更新"; -/* 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:507; prefs.py:843; */ +/* 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:600; prefs.py:866; */ "Always on top" = "永远在最前"; -/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:636; */ +/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:730; */ "Unknown" = "未知"; -/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:705; */ +/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:799; */ "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:712; edsm.py:219; stats.py:50; theme.py:227; */ +/* 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:806; edsm.py:257; stats.py:52; theme.py:227; */ "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:714; EDMarketConnector.py:1072; */ +/* 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:808; EDMarketConnector.py:1263; */ "Role" = "角色"; -/* 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:714; EDMarketConnector.py:1082; EDMarketConnector.py:1105; stats.py:363; */ +/* 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:808; EDMarketConnector.py:1273; EDMarketConnector.py:1296; stats.py:367; */ "Ship" = "飞船"; -/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:715; */ +/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:809; */ "Suit" = "套装"; -/* 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:716; prefs.py:605; stats.py:365; */ +/* 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:810; prefs.py:606; stats.py:369; */ "System" = "星系"; -/* 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:717; prefs.py:623; prefs.py:738; stats.py:366; */ +/* 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:811; prefs.py:624; prefs.py:761; stats.py:370; */ "Station" = "站点"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:720; EDMarketConnector.py:735; EDMarketConnector.py:738; EDMarketConnector.py:1791; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:814; EDMarketConnector.py:829; EDMarketConnector.py:832; EDMarketConnector.py:1991; */ "File" = "文件"; -/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:721; EDMarketConnector.py:736; EDMarketConnector.py:739; */ +/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:815; EDMarketConnector.py:830; EDMarketConnector.py:833; */ "Edit" = "编辑"; -/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:722; */ +/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:816; */ "View" = "显示"; -/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:723; */ +/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:817; */ "Window" = "窗口"; -/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:724; EDMarketConnector.py:737; EDMarketConnector.py:740; */ +/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:818; EDMarketConnector.py:831; EDMarketConnector.py:834; */ "Help" = "帮助"; -/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:727; EDMarketConnector.py:753; EDMarketConnector.py:1347; */ +/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:821; EDMarketConnector.py:847; EDMarketConnector.py:1550; */ "About {APP}" = "关于 {APP}"; -/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:729; EDMarketConnector.py:752; */ +/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:823; EDMarketConnector.py:846; */ "Check for Updates..." = "检查版本更新…"; -/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:730; EDMarketConnector.py:744; */ +/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:824; EDMarketConnector.py:838; */ "Save Raw Data..." = "保存原始数据…"; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:731; EDMarketConnector.py:743; stats.py:360; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:825; EDMarketConnector.py:837; stats.py:364; */ "Status" = "状态"; -/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:732; EDMarketConnector.py:750; */ +/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:826; EDMarketConnector.py:844; */ "Privacy Policy" = "隐私政策"; -/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:733; EDMarketConnector.py:751; EDMarketConnector.py:1381; */ +/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:827; EDMarketConnector.py:845; EDMarketConnector.py:1584; */ "Release Notes" = "更新说明"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:745; EDMarketConnector.py:1792; prefs.py:254; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:839; EDMarketConnector.py:1992; prefs.py:254; */ "Settings" = "设置"; -/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:746; */ +/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:840; */ "Exit" = "退出"; -/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:749; */ +/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:843; */ "Documentation" = "帮助文档"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:756; ttkHyperlinkLabel.py:42; */ +/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:850; ttkHyperlinkLabel.py:42; */ "Copy" = "复制"; -/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:762; */ +/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:856; */ "Logging in..." = "登录中…"; -/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:778; EDMarketConnector.py:1215; */ +/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:872; EDMarketConnector.py:1415; */ "Authentication successful" = "身份验证成功"; -/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:809; */ +/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:903; */ "You're not docked at a station!" = "你尚未靠站!"; -/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:817; */ +/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:911; */ "Station doesn't have anything!" = "此站点没有任何货物!"; -/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:822; */ +/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:916; */ "Station doesn't have a market!" = "此站点没有货物市场!"; -/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI to save to file; stats.py: Fetching data from Frontier CAPI in order to display on File > Status; In files: EDMarketConnector.py:867; EDMarketConnector.py:1428; stats.py:279; */ +/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:990; */ "Fetching data..." = "取得数据…"; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:880; */ +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1031; */ "CAPI: No commander data returned" = "CAPI:没有指挥官数据"; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:884; stats.py:297; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1035; stats.py:298; */ "Who are you?!" = "你是谁?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:890; stats.py:307; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1041; stats.py:308; */ "Where are you?!" = "你在哪里?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:894; stats.py:312; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1048; stats.py:316; */ "What are you flying?!" = "你在飞什么?!"; -/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1026; */ +/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1171; */ +"Frontier CAPI server error" = "Frontier CAPI 服务器错误"; + +/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1210; */ "Last updated at %H:%M:%S" = "最后更新于 %H:%M:%S"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1052; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1238; */ "Fighter" = "舰载机驾驶员"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1053; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1239; */ "Gunner" = "炮手"; -/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1054; */ +/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1240; */ "Helm" = "舵手"; -/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1298; */ +/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1498; */ "cooldown {SS}s" = "距离下次更新还有 {SS} 秒"; -/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1407; prefs.py:304; */ +/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1610; prefs.py:305; */ "OK" = "确定"; -/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1489; */ +/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1677; */ "Shutting down..." = "应用关闭中…"; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1780:1786; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:1980:1986; */ "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "一项或多项你所启用的插件尚不支持 Python 3.x,详情请见 {FILE} > {SETTINGS} > {PLUGINS}。请检查上述插件是否有可用的更新,若没有,则应与插件的开发者就 Python 3.x 的支持问题取得联系。\n\n你也可以禁用某一项插件,只需在其文件夹名称的末尾添加 {DISABLED} 即可。"; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1790; prefs.py:953; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:1990; prefs.py:976; */ "Plugins" = "插件"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:1801; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2001; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC:不支持 Python 3.x 的插件"; /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ @@ -189,7 +181,7 @@ /* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ "Ignore" = "忽略"; -/* 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:194; prefs.py:466; prefs.py:678; prefs.py:711; */ +/* 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:194; prefs.py:467; prefs.py:701; prefs.py:734; */ "Default" = "默认"; /* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ @@ -222,425 +214,431 @@ /* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ "EDDB Journal processing disabled. See Log." = "EDDB Journal Processing 已禁用。请查看诊断日志。"; -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:215; eddn.py:619; eddn.py:969; */ +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:695; eddn.py:1428; */ "Sending data to EDDN..." = "正在向 EDDN 发送数据…"; -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:264; eddn.py:907; eddn.py:942; eddn.py:981; */ +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:312; eddn.py:1364; eddn.py:1399; eddn.py:1440; */ "Error: Can't connect to EDDN" = "错误:无法连接到 EDDN"; -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:282; */ +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:330; */ "EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN 错误:EDDN 禁止了此旧版本的 EDMC 应用。请更新应用。"; -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:288; */ +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:336; */ "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:293; */ +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:341; */ "EDDN Error: Returned {STATUS} status code" = "EDDN 错误:返回错误代码 {STATUS}"; -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:699; */ +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1111; */ "Send station data to the Elite Dangerous Data Network" = "发送站点数据到 Elite Dangerous Data Network"; -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:710; */ +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1122; */ "Send system and scan data to the Elite Dangerous Data Network" = "发送星系和扫描数据到 Elite Dangerous Data Network"; -/* 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:721; */ +/* 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:1133; */ "Delay sending until docked" = "延时更新,靠站时再发送"; -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:785; */ +/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1222; */ "EDDN journal handler disabled. See Log." = "EDDN Journal Handler 已禁用。请查看诊断日志。"; -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:198; */ +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ "Send flight log and Cmdr status to EDSM" = "发送航行日志和指挥官状态到 EDSM"; -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:208; */ +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map 授权凭证"; -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:227; */ +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ "Commander Name" = "指挥官名称"; -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:235; inara.py:237; */ +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ "API Key" = "API Key"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:262; prefs.py:518; prefs.py:1156; prefs.py:1189; stats.py:117; stats.py:136; stats.py:155; stats.py:172; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:519; prefs.py:1173; prefs.py:1206; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ "None" = "无"; -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:358; */ +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ "EDSM Handler disabled. See Log." = "EDSM Handler 已禁用。请查看诊断日志。"; -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:640; edsm.py:745; */ +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ "Error: EDSM {MSG}" = "错误:EDSM {MSG}"; -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:677; edsm.py:740; */ +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ "Error: Can't connect to EDSM" = "错误:无法连接到 EDSM"; -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:216; */ +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ "Send flight log and Cmdr status to Inara" = "发送航行日志和指挥官状态到 Inara"; -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:228; */ +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ "Inara credentials" = "Inara 授权凭证"; -/* inara.py: INARA support disabled via killswitch; In files: inara.py:335; */ +/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ "Inara disabled. See Log." = "Inara 已禁用。请查看诊断日志。"; - -/* Raised when the Companion API server thinks that the user has not purchased E:D. i.e. doesn't have the correct 'SKU'. [companion.py] */ -"Error: Frontier server SKU problem" = "错误:Frontier 服务器检测到 SKU 不匹配"; -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1549; inara.py:1562; */ +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ "Error: Inara {MSG}" = "错误:Inara {MSG}"; /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:250; */ "Preferences" = "偏好"; -/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:346; */ +/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:347; */ "Please choose what data to save" = "请选择想要保存的数据"; -/* prefs.py: Settings > Output option; In files: prefs.py:352; */ +/* prefs.py: Settings > Output option; In files: prefs.py:353; */ "Market data in CSV format file" = "货物市场数据,以 CSV 文件格式"; -/* prefs.py: Settings > Output option; In files: prefs.py:361; */ +/* prefs.py: Settings > Output option; In files: prefs.py:362; */ "Market data in Trade Dangerous format file" = "货物市场数据,以 Trade Dangerous 文件格式"; -/* prefs.py: Settings > Output option; In files: prefs.py:371; */ +/* prefs.py: Settings > Output option; In files: prefs.py:372; */ "Ship loadout" = "飞船配置"; -/* prefs.py: Settings > Output option; In files: prefs.py:381; */ +/* prefs.py: Settings > Output option; In files: prefs.py:382; */ "Automatically update on docking" = "靠站时自动更新"; -/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:390; prefs.py:409; */ +/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:391; prefs.py:410; */ "File location" = "保存位置"; -/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:398; prefs.py:448; */ +/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:399; prefs.py:449; */ "Change..." = "更改…"; -/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:401; prefs.py:451; */ +/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:402; prefs.py:452; */ "Browse..." = "浏览…"; -/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:416; */ +/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:417; */ "Output" = "导出"; -/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:442; prefs.py:457; */ +/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:443; prefs.py:458; */ "E:D journal file location" = "E:D 日志文件位置"; -/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:482; */ +/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:483; */ "Keyboard shortcut" = "键盘快捷键"; -/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:484; */ +/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:485; */ "Hotkey" = "快捷键"; -/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:493; */ +/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:494; */ "Re-start {APP} to use shortcuts" = "重启 {APP} 以使用快捷键"; -/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:502; */ +/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:503; */ "{APP} needs permission to use shortcuts" = "{APP} 需要权限以使用快捷键"; -/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:507; */ +/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:508; */ "Open System Preferences" = "打开系统偏好设置"; -/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:529; */ +/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:530; */ "Only when Elite: Dangerous is the active app" = "仅当 Elite: Dangerous 在前台运行时生效"; -/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:540; */ +/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:541; */ "Play sound" = "发出提示音"; -/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:555; */ +/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:556; */ "Disable Automatic Application Updates Check when in-game" = "当我在玩游戏时,禁止此应用自动检查版本更新。"; -/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:568; */ +/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:569; */ "Preferred websites" = "首选网站"; -/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:579; */ +/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:580; */ "Shipyard" = "配船"; -/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:591; */ +/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:592; */ "Use alternate URL method" = "若链接过长无法打开,请勾选此项"; -/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:644; */ +/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:645; */ "Log Level" = "诊断模式"; -/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:672; */ +/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:673; */ "Configuration" = "通用"; -/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:685; */ +/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:682; */ +"Main UI privacy options" = "用户界面隐私选项"; + +/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:687; */ +"Hide private group name in UI" = "在用户界面中隐藏私人组 (private group) 名称"; + +/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:691; */ +"Hide multi-crew captain name" = "在用户界面中隐藏多人共乘 (multi-crew) 时的舰长名称"; + +/* prefs.py: Preferences privacy tab title; In files: prefs.py:695; */ +"Privacy" = "隐私"; + +/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:708; */ "Normal text" = "普通文本"; -/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:687; */ +/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:710; */ "Highlighted text" = "重点文本"; -/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:696; */ +/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:719; */ "Language" = "语言"; -/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:706; */ +/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:729; */ "Theme" = "主题"; -/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:717; */ +/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:740; */ "Dark" = "深色"; -/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:724; */ +/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:747; */ "Transparent" = "透明"; -/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:770; */ -"UI Scale Percentage" = "UI 缩放比例"; +/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:793; */ +"UI Scale Percentage" = "用户界面缩放比例"; -/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:791; */ +/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:814; */ "100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 为默认设置{CR}需要重启应用{CR}以使修改生效"; -/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:801; */ +/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:824; */ "Main window transparency" = "主窗口透明度"; -/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:821:824; */ +/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:844:847; */ "100 means fully opaque.{CR}Window is updated in real time" = "100 为完全不透明{CR}效果实时更新"; -/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:860; */ +/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:883; */ "Appearance" = "外观"; -/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:875; */ +/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:898; */ "Plugins folder" = "插件文件夹"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:882; */ +/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:905; */ "Open" = "打开"; -/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:890; */ +/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:913; */ "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "提示:想要禁用某一项插件,{CR}你可以添加 {EXT} 到其文件夹名称中"; -/* prefs.py: Label on list of enabled plugins; In files: prefs.py:901; */ +/* prefs.py: Label on list of enabled plugins; In files: prefs.py:924; */ "Enabled Plugins" = "已启用插件"; -/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:921; */ +/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:944; */ "Plugins Without Python 3.x Support:" = "不支持 Python 3.x 的插件:"; -/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:929; */ +/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:952; */ "Information on migrating plugins" = "关于插件迁移"; -/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:944; */ +/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:967; */ "Disabled Plugins" = "已禁用插件"; -/* stats.py: Cmdr stats; In files: stats.py:51; */ +/* stats.py: Cmdr stats; In files: stats.py:53; */ "Balance" = "资金结余"; -/* stats.py: Cmdr stats; In files: stats.py:52; */ +/* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "贷款"; -/* stats.py: Ranking; In files: stats.py:57; */ +/* stats.py: Ranking; In files: stats.py:59; */ "Combat" = "战斗等级"; -/* stats.py: Ranking; In files: stats.py:58; */ +/* stats.py: Ranking; In files: stats.py:60; */ "Trade" = "贸易等级"; -/* stats.py: Ranking; In files: stats.py:59; */ +/* stats.py: Ranking; In files: stats.py:61; */ "Explorer" = "探索等级"; -/* stats.py: Ranking; In files: stats.py:60; */ +/* stats.py: Ranking; In files: stats.py:62; */ "CQC" = "竞技场等级"; -/* stats.py: Ranking; In files: stats.py:61; */ +/* stats.py: Ranking; In files: stats.py:63; */ "Federation" = "联邦军衔"; -/* stats.py: Ranking; In files: stats.py:62; */ +/* stats.py: Ranking; In files: stats.py:64; */ "Empire" = "帝国头衔"; -/* stats.py: Ranking; In files: stats.py:63; */ +/* stats.py: Ranking; In files: stats.py:65; */ "Powerplay" = "势力"; -/* stats.py: Combat rank; In files: stats.py:71; */ +/* stats.py: Combat rank; In files: stats.py:73; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:72; */ +/* stats.py: Combat rank; In files: stats.py:74; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:76; stats.py:139; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:78; */ +/* stats.py: Combat rank; In files: stats.py:80; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:79; stats.py:90; stats.py:101; stats.py:112; */ +/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:82; */ +/* stats.py: Trade rank; In files: stats.py:84; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:83; */ +/* stats.py: Trade rank; In files: stats.py:85; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:93; */ +/* stats.py: Explorer rank; In files: stats.py:95; */ "Aimless" = "Aimless"; - -/* Appearance setting. [EDMarketConnector.py] */ -"Minimize to system tray" = "最小化到系统托盘"; -/* stats.py: Explorer rank; In files: stats.py:94; */ +/* stats.py: Explorer rank; In files: stats.py:96; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:104; */ +/* stats.py: CQC rank; In files: stats.py:106; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:105; */ +/* stats.py: CQC rank; In files: stats.py:107; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: CQC rank; In files: stats.py:108; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:109; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:110; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:111; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:112; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:113; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:118; */ +/* stats.py: Federation rank; In files: stats.py:120; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:119; */ +/* stats.py: Federation rank; In files: stats.py:121; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:122; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:123; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:124; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:125; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:126; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:127; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:128; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:129; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:130; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:131; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:132; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:133; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:137; */ +/* stats.py: Empire rank; In files: stats.py:139; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:138; */ +/* stats.py: Empire rank; In files: stats.py:140; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:142; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:141; */ +/* stats.py: Empire rank; In files: stats.py:143; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:144; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:145; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:146; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:147; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:148; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:149; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:150; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:151; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:152; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:156; */ +/* stats.py: Power rank; In files: stats.py:158; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:157; */ +/* stats.py: Power rank; In files: stats.py:159; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:160; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:161; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:162; */ "Rating 5" = "Rating 5"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:367; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ "Value" = "价值"; -/* stats.py: Status dialog title; In files: stats.py:376; */ +/* stats.py: Status dialog title; In files: stats.py:380; */ "Ships" = "所有飞船"; From 6f31e18947e0ea1bb3b91b02409f31d75d1ff00a Mon Sep 17 00:00:00 2001 From: Athanasius <github@miggy.org> Date: Mon, 25 Oct 2021 11:02:54 +0100 Subject: [PATCH 316/316] Release 5.2.0: appversion & changelog --- ChangeLog.md | 45 +++++++++++++++++++-------------------------- config.py | 2 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ab487120..b4f5a1f8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,30 +27,10 @@ produce the Windows executables and installer. --- -Pre-Release 5.2.0 beta2 +Release 5.2.0 === -* Trying to use `File` > `Status` when the current commander is unknown, or - there is has been no CAPI data retrieval yet, will now result in the 'bad' - sound being played and an appropriate status line message. - -* Pressing the 'Update' button when in space (not docked, not on a body - surface) will no longer cause a spurious "Docked but unknown station: EDO - Settlement?" message. - -Pre-Release 5.2.0 beta2 -=== - -* Don't save, for later retry, any EDDN messages that are met with 'unknown - schema' from the server. This should alleviate any "laggy UI" problems that - their accumulation can cause. - -Pre-Release 5.2.0 beta1 -=== - -* Now built using Python 3.9.7. - -* `Update` button is disabled if CQC/Arena is detected. +* The 'Update' button is disabled if CQC/Arena is detected. * Frontier CAPI queries now run in their own thread. There should be no change in functionality for users. This affects both EDMarketConnector @@ -60,6 +40,10 @@ Pre-Release 5.2.0 beta1 query. **Currently if data has not yet been cached nothing will happen when trying to use this**. +* Trying to use `File` > `Status` when the current commander is unknown, or + there is has been no CAPI data retrieval yet, will now result in the 'bad' + sound being played and an appropriate status line message. + * `File` > `Save Raw Data` also now uses the cached CAPI data, rather than causing a fresh query. This will write an empty JSON `{}` if no data is yet available. @@ -77,8 +61,7 @@ Pre-Release 5.2.0 beta1 logical groups for `--help` output. * Support added for several new EDDN schemas relating to specific Journal - events. Until the live EDDN server is updated (planned for 2021-10-21) you - will see minor error logging when this fails due to 'unknown schema'. + events. The live EDDN server has been updated to support these. Schema support added for: - `codexentry/1` @@ -87,9 +70,16 @@ Pre-Release 5.2.0 beta1 - `navroute/1` - `scanbarycentre/1` +* If a message to EDDN gets an 'unknown schema' response it will **NOT** be + saved in the replaylog for later retries, instead being discarded. + Bug Fixes --- +* Pressing the 'Update' button when in space (not docked, not on a body + surface) will no longer cause a spurious "Docked but unknown station: EDO + Settlement?" message. + * A bug preventing `--force-localserver-auth` from working has been fixed. * `horizons` and `odyssey` flags should now always be set properly on *all* @@ -98,6 +88,8 @@ Bug Fixes Developers --- +* Now built using Python 3.9.7. + * New `journal_entry_cqc()` function for plugins to receive journal events *specifically and **only** when the player is in CQC/Arena*. This allows for tracking things that happen in CQC/Arena without polluting @@ -122,8 +114,9 @@ Developers command-line argument. This needs to be the *full* 'upload' URL, i.e. for the live instance this is `https://eddn.edcd.io:4430/upload/`. -* New command-line argument `--eddn-tracking-ui` to track the EDDN handling - code's idea of the current Journal and Status.json Body(Name|ID). +* New command-line argument `--eddn-tracking-ui` to track the EDDN plugin's + idea of the current BodyName and BodyID, from both the Journal and + Status.json. --- diff --git a/config.py b/config.py index 93078020..d690da56 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ appcmdname = 'EDMC' # <https://semver.org/#semantic-versioning-specification-semver> # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.2.0-beta3' +_static_appversion = '5.2.0' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD'