From 8b3ef230f55a1a8cd93890f4ce9cbef21e453d2f Mon Sep 17 00:00:00 2001
From: Phoebe <40956085+C1701D@users.noreply.github.com>
Date: Sat, 23 Mar 2024 16:32:21 +0100
Subject: [PATCH 01/49] Merge pull request #2181 from
 HullSeals/fix/2176/TCE-reported-split-issue

[2176] Fix Outfitting Split
LGTM
---
 outfitting.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/outfitting.py b/outfitting.py
index 2bb47c6d..a5ccb4be 100644
--- a/outfitting.py
+++ b/outfitting.py
@@ -69,7 +69,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None:  # noqa: C901, CCR0
     # Armour - e.g. Federation_Dropship_Armour_Grade2
     if name[-2] == 'armour':
         # Armour is ship-specific, and ship names can have underscores
-        ship_name, armour_grade = module["name"].lower().rsplit("_", 2)[0:2]
+        ship_name, armour, armour_grade = module["name"].lower().rsplit("_", 2)[0:3]
         if ship_name not in ship_map:
             raise AssertionError(f"Unknown ship: {ship_name}")
         new['category'] = 'standard'

From 3cd6b57d5f1e90f38a9a3c182363a141d0871fc2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 23 Mar 2024 11:39:29 -0400
Subject: [PATCH 02/49] [Lang] Update French

---
 L10n/fr.strings | 44 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/L10n/fr.strings b/L10n/fr.strings
index 93e09777..5d53fd24 100644
--- a/L10n/fr.strings
+++ b/L10n/fr.strings
@@ -1,21 +1,59 @@
+/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
+"Send flight log and CMDR status to EDSM" = "Envoyer les données de vol et le status de CMDR à EDSM";
+
+/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
+"Open Log Folder" = "Ouvrir le dossier de log";
+
+/* inara.py:Text Inara Show API key; In files: inara.py:305; */
+"Show API Key" = "Afficher la clé API";
 /* Language name */
 "!Language" = "Français";
 
+/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */
+"Error: Frontier CAPI didn't respond" = "L'API Compagnon de Frontier ne répond pas";
+
 /* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */
 "Error: Frontier server is lagging" = "Erreur : Le serveur Frontier ne répond pas";
 
+/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */
+"Docked but unknown station: EDO Settlement?" = "Amarré, mais station inconnue : colonie EDO ?";
+
 /* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */
 "Error: Invalid Credentials" = "Erreur : Identifiants invalides";
 
 /* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
 "Error: Wrong Cmdr" = "Erreur : Cmdr incorrect";
 
+/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
+"Error" = "Erreur";
+
+/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */
+"Error: Couldn't check token customer_id" = "Erreur : Impossible de vérifier le jeton customer_id";
+
+/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */
+"Error: customer_id doesn't match!" = "Erreur : customer_id ne correspond pas !";
+
+/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
+"Error: unable to get token" = "Erreur : impossible d'obtenir le jeton";
+
+/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
+"Frontier CAPI down for maintenance" = "L'API Compagnon de Frontier est en panne pour maintenance";
+
+/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
+"Frontier CAPI query failure" = "Échec de la requête à l'API Compagnon Frontier";
+
 /* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */
 "Update" = "Mettre à jour";
 
 /* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */
 "Always on top" = "Toujours visible";
 
+/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
+"Unknown" = "Inconnue";
+
+/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
+"Error: Check E:D journal file location" = "Erreur : Vérifier l'emplacement du fichier de journal Elite : Dangerous";
+
 /* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */
 "Cmdr" = "Cmd";
 
@@ -64,6 +102,12 @@
 /* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
 "Documentation" = "Documentation";
 
+/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
+"Troubleshooting" = "Résolution de problème";
+
+/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
+"Report A Bug" = "Signaler un bug";
+
 /* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
 "Privacy Policy" = "Politique de Confidentialité";
 

From b9f22f0f3a433159cb349a9ffaf6045e63c24f75 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 23 Mar 2024 12:01:54 -0400
Subject: [PATCH 03/49] [5.10.3] Update Changelog and Version String

---
 ChangeLog.md       | 19 +++++++++++++++++++
 config/__init__.py |  2 +-
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index 480d80a0..6ee5749f 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -6,6 +6,25 @@ This is the master changelog for Elite Dangerous Market Connector.  Entries are
       in the source (not distributed with the Windows installer) for the
       currently used version.
 ---
+Release 5.10.3
+===
+This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations. 
+
+We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
+For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
+
+**Changes and Enhancements**
+* Updated French Translations
+
+**Bug Fixes**
+* Fixed a bug that crashed the outfitting system when encountering armor. (Thanks TCE team for identifying this one!)
+
+**Plugin Developers**
+* modules.p and ships.p are deprecated, and slated
+for removal in the next major release! Please look for that change coming soon. 
+* Note to plugin developers: The `openurl()` function in ttkHyperlinkLabel has been deprecated,
+and slated for removal in the next major release! Please migrate to `webbrowser.open()`.
+
 Release 5.10.2
 ===
 This release contains updated dependencies, some bug fixes, a few minor enhancements to some supporting files, 
diff --git a/config/__init__.py b/config/__init__.py
index 0d16693c..3acb2edd 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -54,7 +54,7 @@ appcmdname = 'EDMC'
 # <https://semver.org/#semantic-versioning-specification-semver>
 # Major.Minor.Patch(-prerelease)(+buildmetadata)
 # NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
-_static_appversion = '5.10.2'
+_static_appversion = '5.10.3'
 
 _cached_version: semantic_version.Version | None = None
 copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'

From 813cf92521045cbe121c5215e4e2d56859738ce5 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 23 Mar 2024 16:54:13 -0400
Subject: [PATCH 04/49] [1133] Add ContextMenu Globally

---
 monitor.py           |  2 ++
 myNotebook.py        | 54 +++++++++++++++++++++++++++++++++++++++++---
 requirements-dev.txt | 14 ++++++------
 3 files changed, 60 insertions(+), 10 deletions(-)

diff --git a/monitor.py b/monitor.py
index 8364f95b..e7f99543 100644
--- a/monitor.py
+++ b/monitor.py
@@ -63,6 +63,8 @@ elif sys.platform == 'win32':
     GetWindowText = ctypes.windll.user32.GetWindowTextW
     GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
     GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
+    GetWindowTextLength.argtypes = [ctypes.wintypes.HWND]
+    GetWindowTextLength.restype = ctypes.c_int
 
     GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd
 
diff --git a/myNotebook.py b/myNotebook.py
index 6fa35774..02b1eb45 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -77,7 +77,7 @@ class Label(tk.Label):
     """Custom tk.Label class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
+        # This format chosen over `sys.platform in (...)` as mypy and friends don't understand that
         if sys.platform in ('darwin', 'win32'):
             kw['foreground'] = kw.pop('foreground', PAGEFG)
             kw['background'] = kw.pop('background', PAGEBG)
@@ -87,7 +87,55 @@ class Label(tk.Label):
         tk.Label.__init__(self, master, **kw)  # Just use tk.Label on all platforms
 
 
-class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry):  # type: ignore
+class EntryMenu(ttk.Entry):
+    """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
+
+    def __init__(self, *args: ttk.Frame | None, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+
+        self.menu = tk.Menu(self, tearoff=False)
+        self.menu.add_command(label="Copy", command=self.copy)
+        self.menu.add_command(label="Cut", command=self.cut)
+        self.menu.add_separator()
+        self.menu.add_command(label="Paste", command=self.paste)
+        self.menu.add_separator()
+        self.menu.add_command(label="Select All", command=self.select_all)
+
+        self.bind("<Button-3>", self.display_popup)
+
+    def display_popup(self, event: tk.Event) -> None:
+        """Display the menu popup."""
+        self.menu.post(event.x_root, event.y_root)
+
+    def select_all(self) -> None:
+        """Select all the text within the Entry."""
+        self.selection_range(0, tk.END)
+        self.focus_set()
+
+    def copy(self) -> None:
+        """Copy the selected Entry text."""
+        if self.selection_present():
+            self.clipboard_clear()
+            self.clipboard_append(self.selection_get())
+
+    def cut(self) -> None:
+        """Cut the selected Entry text."""
+        if self.selection_present():
+            self.copy()
+            self.delete(tk.SEL_FIRST, tk.SEL_LAST)
+
+    def paste(self) -> None:
+        """Paste the selected Entry text."""
+        if self.selection_present():
+            self.delete(tk.SEL_FIRST, tk.SEL_LAST)
+        try:
+            text = self.clipboard_get()
+            self.insert(tk.INSERT, text)
+        except tk.TclError:
+            pass  # No text in clipboard or clipboard is not text
+
+
+class Entry(sys.platform == 'darwin' and tk.Entry or EntryMenu or ttk.Entry):  # type: ignore
     """Custom t(t)k.Entry class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
@@ -95,7 +143,7 @@ class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry):  # type: ignore
             kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
             tk.Entry.__init__(self, master, **kw)
         else:
-            ttk.Entry.__init__(self, master, **kw)
+            EntryMenu.__init__(self, master, **kw)
 
 
 class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):  # type: ignore
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 73ab5465..41c3a928 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -5,7 +5,7 @@ wheel
 # We can't rely on just picking this up from either the base (not venv),
 # or venv-init-time version.  Specify here so that dependabot will prod us
 # about new versions.
-setuptools==69.1.1
+setuptools==69.2.0
 
 # Static analysis tools
 flake8==7.0.0
@@ -18,14 +18,14 @@ flake8-noqa==1.4.0
 flake8-polyfill==1.0.2
 flake8-use-fstring==1.4
 
-mypy==1.8.0
+mypy==1.9.0
 pep8-naming==0.13.3
-safety==2.3.5
-types-requests==2.31.0.20240125
+safety==3.0.1
+types-requests==2.31.0.20240311
 types-pkg-resources==0.1.3
 
 # Code formatting tools
-autopep8==2.0.4
+autopep8==2.1.0
 
 # Git pre-commit checking
 pre-commit==3.6.2
@@ -38,9 +38,9 @@ grip==4.6.2
 py2exe==0.13.0.1; sys_platform == 'win32'
 
 # Testing
-pytest==8.0.2
+pytest==8.1.1
 pytest-cov==4.1.0  # Pytest code coverage support
-coverage[toml]==7.4.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
+coverage[toml]==7.4.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
 coverage-conditional-plugin==0.9.0
 # For manipulating folder permissions and the like.
 pywin32==306; sys_platform == 'win32'

From ccda74c8f103cff0d63d3d605a0de076f53a77d9 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 23 Mar 2024 17:05:14 -0400
Subject: [PATCH 05/49] [Minor] Remove Crappy Type Hint

---
 myNotebook.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/myNotebook.py b/myNotebook.py
index 02b1eb45..63bb7dc8 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -90,7 +90,7 @@ class Label(tk.Label):
 class EntryMenu(ttk.Entry):
     """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
 
-    def __init__(self, *args: ttk.Frame | None, **kwargs) -> None:
+    def __init__(self, *args, **kwargs) -> None:
         super().__init__(*args, **kwargs)
 
         self.menu = tk.Menu(self, tearoff=False)

From 791a0c80c2c0b202d27a636c36302229af88c328 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 18:36:03 -0400
Subject: [PATCH 06/49] [Minor] Remove Deprecated Files and Functions

---
 build.py             |   2 --
 modules.p            | Bin 44802 -> 0 bytes
 ships.p              | Bin 920 -> 0 bytes
 ttkHyperlinkLabel.py |  41 -----------------------------------------
 4 files changed, 43 deletions(-)
 delete mode 100644 modules.p
 delete mode 100644 ships.p

diff --git a/build.py b/build.py
index 1bc96765..904c4c5b 100644
--- a/build.py
+++ b/build.py
@@ -76,10 +76,8 @@ def generate_data_files(
                 "ChangeLog.md",
                 "snd_good.wav",
                 "snd_bad.wav",
-                "modules.p",  # TODO: Remove in 6.0
                 "modules.json",
                 "ships.json",
-                "ships.p",  # TODO: Remove in 6.0
                 f"{app_name}.ico",
                 f"resources/{appcmdname}.ico",
                 "EDMarketConnector - TRACE.bat",
diff --git a/modules.p b/modules.p
deleted file mode 100644
index c48f76c4894fdb061e0a1967f388afe819e679c4..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 44802
zcmbVVO{^SOR(2f6`I!tP*p3}LKQI4ICIgvd@-t#2KMAC#(UT#9k%Cz8RIj_=du{ih
zy1E^EMnPU6VDWb234)b3NU#~PMzfeTg3YX13}S`E27v&fS#av!ulm-h^K);<o6)4c
z^WE>9bMLw5R@JM%f9}Ws^OG~--|5!|^T}j1SdHhi`oZbfes?(>El0!O9uHOze)Qn&
z-+OTV^g=bH|8`I<r}Opl;CNXLN8ftz#e>sx)2gl??4PMM#QTIeIlup?dhvsJ@v-{!
z662nZm&-YgZd_iDD$;ppw7L9THLC{m*{~AgXlT-<Ux&1ptm{zs-TL%OpPviV5L_c6
zo~`PIAx~U_vP+5;M7`6*GZAt<y^CB5UA){_)`R(aWe73=NtOXf;+_{n2#FFRgg$>b
znyinj$#|yByO>%N0<phepPCTJ{p<Slnlca<Y=F3A0$mx*50};S_INxtf@>il_*ZNW
z=)G2-UX`P8xjKfcCCufP=Z+!P03@vjAW3~*3?Y<Ch!A>luwJdqNuXVYL|Wb2Rp_I2
z`5b5F;^cL%jIKXF99L7SJ%`ocwh=@V0*zmHSRnK&%~53_uGRo?$vx2Bi6Oc>Tn_}<
zm${}M2*Q7(K7FwxJb{FC9md5I+oj=rvN##f4B6^s2;MQQA@v5WhaNc>sQ|b<0$dnX
z$CHsEPPqta=Tu7wdxCT`S}sdm=A!8G3!~{`yc``+$zP7mRpg<POb?axzNC*Rgc~^F
z!Bz0FFl<_oxE0u#q~69P?X8DwETLRQ3EkJAd)0JHvUiHY9T1dL6>~t4PQ~a&bvUlw
zgmLi%d*x_EqN}DQq^YyKGolh~%<L>QIg*7YcV2S@2&X)->)LQRU(_dKt2^uJRiSY0
zT^=wM3fDfXPrGXSQ0`g=%v~e!o1LI-n45;gNL?8|Ia!8Jo?g%C5$<L!gwH#jF^<<W
zC!0BB@-v4_9=>XiE_`#a!mBGsBRYjms@cF=b`}C&T%cak;DGu!sh5-pu3E=%wS>8N
zQmrRujnl3|<s-T+6rQCPjWcs~@;X;W*IzkV&`D=_rwQb6HD4|k_a;=*j>dOK!-Gk+
zJjO|qcFI>h=yWt3ucz_UNgK9HTTiRWBtl8S3xRgF8n0)!XY<c#g05FX`rm`qcy=$K
z_HO*wKQ{mVspVY_*VDrz+O?<Sx*k(YVaqk3J-N{}fnBYa%h9SAhS!2#3)>@pF@mS-
z$!a{PW-}wSFIli2oq|&}zAX8G>k;lWz{9E@4-m>_)2eIX3>h_$TafbHUkSsHs_CSv
zX?etPPuA_Edxn~S4Z-K-RSnXUq|$ILA-0D-Edg?HGOAXa`P!p89#0Re$pI7HQv?WC
zeT}OIrYBn`;<|enbw2b4)8~Rt#0R_7sAdzHIsTlu%$>e0uJ;_41bqDNbuIGU0o1UE
z83zaSm@5=qLl>Y%yE}#+YK4N25$eW(%I~~x#M0WR$3GaocQBxZQa5)NPtJF$qKl3@
zNhvz+ET!mgZ>8kJK01`CLA9=_IPy}iPK%T3=x9={XM+=phqgP!I85g2VR_UHv=KD}
zZA8sL8&NaRB5Kjg<#;+O_tHQcUK(h_O9O3qX`qFdVZRwJsVR^<Q*U;J9l6&MnETl4
zO$oBWJ?d|kpf~+f+#{&{d8f#%S<CZ{O~wmeAKvu`2;iQqw%HpsPcoMC*?2&=JZ5y`
za5}2$HQfQxR#ezHXwluC&X=pI-JyFy3LRVrq}ROAV;#}pC(T)TvyS7Q-(w81kXW7g
zjV2by>uO2&(q;$8R8GRW*tA+b?l1uyI1lt&jC?^Uqvo}(L0X@5J#WwxR4@eB<PM?k
zNN;<h^r)5$!e#Nm)+dw@)+z8-fM-pjdqK*Pb162t8lE><c)D8j*ktLsn$To7J!Cze
z-QG+dP;09$RQM@9+1Y^ART9>n0^_5H$XTd^@pQ4C)Hvi(i3nRVp~ti1gX(a3IH_0d
zs^_%9sXh=sab$=tko9O>PX{N<<Ik(*$q)^HFV$f^=y*LHVXC$U>q9DLN5gTwpnYR=
zH1vkhEw~S10a-(Q1yyRG+I&*%yXT><cFIsF$x%ud#r3vH?@<Mi+!xyUwm~Z%wI}uN
z$f_4n-?A!cP8_UgO&qLfOdPCeOB}3dO2QFI9_HB@DDq_v{4v!6)J-+D5uwh=;UcP+
zMuHn7YcoTPY|M<XX-fqWO{HNzJB|&^W@2`jwF8=PTs@`52`Q<1@v+Fb#{z|E%l9KO
zq_PGm#ugbLG8D(yEL4L5bzJnMVBXeIHd{iQYazM9&{Vq6-jrzX*;zKdXog%z>|#Ei
zt%jqcQFD70f#_^hzliU};C5UzsZZvE+s=O9VVBUJl1@-0mPgv4RcBS`Dy3TeHh@oD
zBto5Mh6sJO86p7YBEo&Cu$gza1vc42`4SL5%R&(mm|lWHVKP7mwD&I83!3knqm4c;
zm*2-|#iOrH&2m_w9c_Ag4Jxjw4^k7_A5YJuLB+=lw;+@B!}YRWX*WeflcwW%GGiTT
z8oV$kx`e%qI+R>v=+5-91CU;xs+!kPHrs;AtYudftEM>Chn`z{o;_0Tn2J<0t`4uK
z-6Q40hnt4WYCNH5bb4PLW`~A22zlMCws3Qi<M>d4h@JzECd0$|yl&ndQtmEpio6ex
zecC=fsPb{KN86`9!N<k^ta&NY$DuddeY)L?LI7)eCyus^cGd9J5D68;!LpjQ&m3se
z43)IU)LSmvU_8j2M4iT=<ZZ^$=dViRjik<4K9W9F`AER<kpj)#(QI>5Iy?_jZybeo
zsso+gem_2EQqeo(*>JQN(Oass)q(pyNDhhV_;h`mLt;YGn_92hqx0vfK9)aQCG7Lm
zMA6zjKE8KY-3v?X{q7C?eG+Lx;Kz9l@@>X)(Sd;FDGl-wV|mqqfaO_w9^QBq9!rG#
zt4-DT7NemTiWLlZTgQA*z{4Hac|I)Q;jZgE9~ba^(#Kp6@tM;b+BLlxv{=sPN114q
zS|A6cHESWF^m3zWI9k!>G8#5_N2<Z*(ZQyxZ{7ueApoh5ryBu-Io%lwua)BEfVjU{
zsCre=>RsZs^j0RlV>27k+vjvpoX~4h?Mw7wEwG8RcVn~N1pf*QV?@4!p}1R6IwH0v
z+QLdL;wIH>dRYrT|F^Kf%-F&Lm5=_kpi~t+r@gV%0-l#x@D;m-1w7xv0-jUJ%=!UU
z@O-D-0-l#x@EWs)1w7xv0-k?b;0IK}^ZVr%@SH5Z845{v@ee~8sOautd}mT@9i2zc
zW!7TBl$r6_Ml+$zjHilRd`Gx=v>r{UcbV1n(wJ7^HZL)in~EA&ZYru=xoN6%<u)<*
zog@~R@(fZrDz`~RuiPe;y>gpW_{wdt49ffw)3IP2*rf7TZj%aNxlJm8<u-@`Ew2I_
zzP6}DmD{93SZ<R_VYy8zhUGShfm(8@HLMr(9G-3pdzDb`Qo5n26QzctN|YL=8c}K?
zDn!;$)Q3`wR2@n!Qf(-;NR^?~BGrYgg{TUp7O5tbTBM3lYLV(esYR*=Sqo7MN-a_)
zD78p+pwuE&fl`Z916~vT!m??J+~x1t@WnyhzXulc$piBgP^<I0B7ISf{nAxpB+6Ea
zktkUuMyXttphc;2jd&(#+b=<<5><ju<*5XnN>d3sl_eJ~rcenwm7@}LDn%veREA2>
zsRX%bKKE(sE<vZ#Q-V%qrv#l!P6;}dn|GBpThpI7(w(32;v`ddwlNlKcpGD}mbWoZ
zYkC_iv9^nh#TwtnDy{WxtkRm_#wxA-ZLCraD6$f@U>mDc6SlERwP72pR3o;rO0}ZM
zO4N*PtWxdR#wyj2ZLCr)*~Ti>6naiV_mR7Ib?EKvJENMOE-y#*s9FwA!cF4NU4YAt
z$!K;=?=pr?hdl|{+dbDlyfbi!4n15n04N|?BDhX3@EAlW3yCP)3zy12JUFWARdh3f
z`=p{=7=Tn63_vOZ1^|xHJx~LXmUiDGi0pW@Pt8l8hCfZ9CrJhXt|$=(16;8g;L0=R
z5O76d5Cy3(5&(!)q6Gj%(tQhq0U~!y07NPb{HfD*$p8REDscb+B5ekU+%<=QNQFTZ
zvwj=^fJh|{06-+QPzD1;()q#wK%~OJpSwMj3;;l+5(fYv(q@PvZ=O*q5GbIMD6TcN
z?r3~`LQl=w2fkurY9KK&G!Rb=eSsi6)jO((G%EcmG<}OIb1x}_kFn}JPRrn9=sKl7
zgGajQpqc^3fEEy2dx^ylJSGKYGy{Z(smf>Y@Q^BC1_%%H#9|w<hyjFaPK*J>yr*)X
zVT4aqFf%|b1BX#;Bo-rpaK;Iz*jO@*aE1zN1_)<pqX{oFG!J&@lVEfDFqrwmgNlI*
zXFMLH!o@(ii-Bex!<I_(q+juiFAo>vY6g$185mbHpZvNWKJQ(Os~J45W?)>+e2(jS
z`1E!$u4eGKnt^dO^DRo(!`mkp<7x(vs~H$qGv6_FJ-nN8F|KCtxSD}+HS-Nz*Tb7D
z7vpLMkE<CNS2J(4bUnP+axt!E@VJ_J&qZT}_vgZc_iA>#Tb|QU5`yaEL9p082-cPd
z!BqAjm^mH<iSi(bOM-;6Ui(L`)FgJ_^KCvKaJ}>WfYT>$t*48Cc(8wN9p3^oY?~de
zYkTV&9pU%S`$ml{VYIjVay<O|w#{@<Y+t<}jD6c?YAd#HwBv?Hxviro>j~Y(7|)L8
zbdPj8qE=pR_S9IF+hFsjKC{3EQ!;+pVV`=QavO|OrVaBN<4{{yFb-@mPMJ0=4va$`
zSHU>2!8m2wu);9TPSH59!8oKX+zo5z!E`=sKGl?;3ENm<ESd{NR$?}6V}-G3J``Dr
z8L^EO#-ce<WF=<BHdYvm=0%Z}m>JtxVJw;(;htwZHx~2Ho1-@U-Q)Ohy*Y1Fm1(N!
ze;<W^B_@%cZhy9)waSkYOZ(pLrC7X5Mu|J=-Y)80i3yqk`nYml(MQ4W{dY>RA4lx>
zOR%3r?BC#ZWEOk+<2@8+gz(1yw((#)UPx2|x50b-gYeI1Zo_u@FPndV<%46u_IBTD
zlkdrh5!>7SxC9(Swzs=i0**1;+x@Hsv&kO+)Mwk0ma)PT>6bLi$}eddD~#1IX_l2=
z(lS;Ut6$PAE5D>=tT0x;q*+#eNy}JathS^TwmYE=xLEMgJaDig`*|#QDd!GWWIB%p
zFXh+4imc|b;H6wTSdqaz7QB?Va98-`-h#HEC4J$)|2(bfblY2sr;pDUtN7Cc@0~tA
zt?o8Qqz8L9{xufB)5n|tPk+`=|LITv>>FpF{@&l8KH2=IP&Xg!pIv){HHv7k{YP#+
zXqx|zZvB}4XTyE#%kcmH<Olz<_mhi1y!96i#OG^|2d(%>`(2gx{^8A^|Ht3{vV;~V
zhy8PrZy$XB7R&nfGTYMxm-&aF{r9+)gZ9sVwEF3-KmI>gW@qj;(R?h*{1klu88Zbw
zBq{Li?LHE(=}7`@o20Xmww1|gyGbgeMXp-4Lse}zNoBNcuAYc$zW^;ZcO_S4v`AU2
z_Dj(I(#%zcwo?|Glsy&H-n{Zi;N3r~ChKh6lJ#VPuuWE+syBbLN66DuLfEDW3je4_
z$P-jT*e2+i;PT$?5C0(aT`{1W6(N`Y-fjtdn<vQq`q>!JN0rr-A?y^`CPNovhTe;W
zd%I7?WT=UHG!_OuQQ&P873b;AJ324VR|#*MFX;WL&dbwQ!rP{8C&uCSqe$2@EDqc2
zZ4(B)SI@<SJ*Fni6^i>`%>lki*!3v%XRXjnP8r1MNlzGf+l0k=d-Ibhl#Wr1mj|GP
zw@n!I{+-Ut6IR08CTusx;h!Hv!mc_AE8%Su2E9)_9uxM2nlP6TcO%<`eHw*6Y=yoO
z&tvKeJ0Gv<2?KANFwEHRpGN>VFHcwrZ<{daMF948IWJFG32&RQ`!Npx@C5X};Y_d+
z-Zo*-d-@c3-)8Mgx6jzyb$M}%xXs(cpm+aFoCjyreBrpP=L>vozT$-K^YJ{D@U?l;
z_3`|a@F73+28ZhK_5$0)=mL2{HUh(4fywx2RIe8c`jV!+x7>N(!2>tpZf-XHA}%G|
zoG_T1Sa$C7``{)}RDkKc<&EEXpQmKUz0IM-U<NpP)_g}q9UmyT)OH*`7%tIK&!dD(
z8Olz#PEn%Q98}6q^X<eEsOU?M3r^6v(i<^4VRCgESJFlAHK<&jZj?|~*nooi3>VbX
z>D3z%>MgELQ%Ab!%{rB<)1nc|iWg8&pW%XfdaU4$2=x|Mr^6v#*A1>tzd|T0UO+*8
zh70O<{Sl$w;_7Vor0Y|ItFw6%%8D0IP@my~`uqNfP;YT{Ha*hy(BSH9WrVWg1r*e0
zxT5;--k>^lKnD3l_KoJ{!6|*CgMJ!ge$Tz*hKF5Lskbmfc`Y<z1-z*QB_r`dUN{o3
ztF&rMM&f0*P#&*=w8Bb8;w7<A9<NtBWh3$OMe@Sm`xHNoEJ3F&)<uitm!Q-9a?v8)
zC1`L{o?Wy^a0wdRlvx)oQd)urH|6r}u)3S?nX*5hps!)jmv%xk$OA)PhAK8eL88qw
ze90nwtd)&2r#r^sqZ@U1q_1d(viz&2K>ACN!8+cY=F5~qZ1^NilDP07OZv2)(H-z*
zO+UQSyrsc6mM`#xxX=nU__RKKOV3jC6*E0@g$5~R;l0xZefFRJuw=9xREP9ijg13r
z$#oiL3(sdu`_ipK%v0b1`Qo9;WDU(LVjh@MX)*G(MQv0Dh-H-rV(2^&337m<iL*5F
zKJL2hjA-RDjL=Ao;m;+ShuhRGEa-a>)!?LFk5~AH`x*KwZ!?eecNMxHEbAOOvlcPt
zopWc_A_jk(G2kIJ4q5WJW{b$JrDc!7`xrWqW})yk9U-$8@Rhc-EEK+$kc+Zllk>vh
z#vD+rCaNJ>R(MAhKMO_mby-DivRQd`a>ENK+@eC3WrbU`pJ$<P%ViaH&t~P#p&M5~
z;TDy&EGyiiJ~a!4TP~|8l{PCcmTp`{xy-V{tzFM8u)-~>tv0JT2xp<#r`)*m&gIhr
z0dVVn0Tgbztm1@hyCsgvStusqLr*JMqg&y?oTY_dL>pd-=)LS^6?_MaOi`(KK+&1b
zp5tJVBr2;8D7uj$6eV3F6vf;D!Hu+?I9RwrWz+$|4WCeyaE(wDZwG{_Mf-*Wf_|!(
z4hXZ!U=l@IV-n@r0YNzJ3=RmwsUkWc2sfBS8P=FY0d_zTPPN|wK{(Ys2L#~;lPI(r
z6F{lNIv@zA>h6FbeAfj*xWOb!sWzM_q7DecseU^k2*2-wAe@-OU&Aa%NAy94W&9>J
z`wnmOw}B=E(e@ya>_H&dgFvndVdY(y%Nux)#ghvbPc9&yTwcFbExdYr5Kk^xJh^~)
za(SIrweTwKK|Hx&@#F#`<Tjrk)4s6UnG6yl=B|VYxe_AeN{A=dn!6nqYwk*jC)b+0
zlEss2&D{=*HFqV%lWWag$>Pbi=5B|@n!6I>$+hOLWbx$k&LXX&4h!!qJcuV3EZ*D&
z#FNYW2~`X4COn8I7c8D!Ks>p;i%_-j9>Rloa>3%s1;mrfdk0ku?;JdcCl@T9TtJfC
zE6x2xx@BF@m+=t-YZ*ER7z-GSZ|Clz1&qoI7@?mISQb^3?@_U8+_~v47<o;mQ=)}{
z{B#tufRIlID+>Yn4i&G?bbJw1yf|yI0s<4p;ecQ|Nm-*{x@a7{Fw=QOaPYdUg#`#G
zbvU4uj!xDnP^xk8noI{2!NE(i77rkx)Zu{A9eWfg)i`)LrW1)!%B!&!4<Mk_;egUz
zdlV?uICv$dBZyGS3$YdtAfVLYfYST+C{U_#@FM(B;ovn`iw6);>TpD*;f9w82(Q3I
z5}pmkKk%Yolbjs(K6Vh_2<g<i3|`dZ3_f~O&W|im#mNxIWE2ovwc6qrB|8IzhpF6V
z@bHi-RR#zT^TeW@Tg0NOXMmWBRG2eB_(U}=1H=+?7)8am7)9aF0O1VP?hFvlP}$1>
z;f%v5HU*1O><t+poT2ib0m7M`ED+8(jAGxgh8EjL1_)<p2gm^73>D7|5Y9M^Vr#J&
z#SW7J!Wr5mGC(+UKMRC24x`w8tf9pwlmWsSXBz=ybS7*`8A^CVly8KyLHI4n;}P9c
z48L>S`Etq!o$saI*!%(x{Wc$+C~T|9-`N+!n>L=oLFdP)JH5O)khP)Y3~(Qb4goo2
z40}h587gS$hAQnBxnVE^X(!4d5!6I&1{H!n>;|=Yx7-{-Y+gAe!blxL4vE=Iqnq(-
zPcdn#m54>)XuHcvz-%W8X2c+YM~wCqlUnSMIV2+H<yoAO%!swfPQ(htB4V_s=ENdm
z#A!wh5_rUD<1(qmHk?BuVqSv9S<Q@Ci|j<KKrAA*lXnXdBTh47kia8GyQ3KgvDfF2
zh?tjPab`3l)*?F*D-er_?dIJ=#E8?37$oqB(H3mRK{OOOBqHV|Se#wWh_%R0#0tbB
zV)yfIA!5X7Mhp^o#2z}-qH8fDCfb%9GW9KH#K76vIh#N7$Pvegg+3<-MaYQM3>h@A
zkloy+1|j@L5dFILgnotka6Y_8AC4N#>8<5^^b6@TYLyNu_RFfY7lxOeJqv1^M$6`>
zsH^sMPqNC6!+)EVEG~uiYfbNk50>jOeYwA`>7!Qh4*<{51K`{A>*1?|N%JY|gK9aY
zpA9)UURJ{q{_@0wU_o4USdiBb>4~*65dXzx1H^x7nLt+})AaUuY$&!65d6B$0lmLP
zU(J=HaJf2$t0l~ZX*E5p457+JXuP0WLf)H{Rz}N}iOXCRUH&!t@S&wiUV|k1Qkc93
zMW5BD7rUHXQ2#2|(_CHLTQxsaqfbEXYMOi6)imv%<IG$aO{U&;uB@4C^>{e?y!p{^
zTEpt{un<sqwq9R1S)h{|y{mE@u2siyy@a{2qK^iBWJpymLM8nXigFS1=;zHZF<P!n
zT;`(aa-ja$6U8q=)Q)NiQ8yrJW64E{%Ul#)4x+x}iQ*R_YG1X4s4pREW64E{%Ul#)
zexZr;cMhiG$#_-KOFR0M^H52qhe~>1(nl1+4V>^G%#^za?;Fz9%g{{ioq8E!e~11Y
zq7mC6=PDHdmq!2$@PRLnUl#KCWg%~K*^;*b2zg;r-ZjK27n3yQViE?gF`I-eep!mT
zGp6>z5+yG}6rG_j$cqs5B&C8~<f6o7E{ZN+iPqEw+%c3{h$PQKB#l?)C_<=$5o#~p
mp+5**FGq${^)hrm+eAvShRD}wRrD@%g$jVnd#zso`u_u+#+$AH

diff --git a/ships.p b/ships.p
deleted file mode 100644
index d289497c9540f529b38d01a580f36eaafcd68c26..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 920
zcmZXSzmC&D5XON^LP$am4hrZjXrKrJ3J@J8zT+aty3^&95@}=a#jD%wT08556i5`5
zi-d%s;SH!L5G4hVz!OmM1XNUvy|$ABSJwRI&o?{YtH%2`8)u5obVJEG4K<SjGSl5t
z5mhwuSg5mi+3kbumT9^X#<G{0X~!w&0RhPTn_#ZEoHHUq>I`FoJ;c1oid(_=vtYK?
zD5FQ3Fo7))zD0O>1yu^fwnz3GS=$vPlp-Rl^qvcev;89BQdJ|aVdjNAJPBp0i&Xyu
znBEi0(>Za*OjMa)H^6j<a;AtA9FImLoBIV!rw&eS><5^u9wQ4JJ|p4rKNB0d?sdF2
z`4gD)p5#efF6kRE&22L0)ONm)op&g%Nx0(zkd$j@60@YB!B6abslr34PN>$lFnEpP
zYvd`JKwagX1G8CGJx#@F(%YeG5YfRnKadsi2TW@hN0$%d_p$Y8kx<1@)sV9Uf32YW
zL~BT<id98;_svyNibBhpaRW@hCZ~6A`;_whNv)9l?R`a<E)HTxKE{z3ZE!y(Ld%7X
z_c7iK$YN&g;NudFJr$PbxPi2_ztpiT&Gifeoe7KRb0#WR`PXrq!(~DT_wG6#J)$)U
zKBMTZ0zGgJ6%mj~Rc+*dtk}c<Sdh=@6@`zST}n=tR_#IVwJF1gT=yDYzf%WGuYE(u
POwk6ZDzEbk(mQ_vKH60e

diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index 0cc19b4e..0ca87fc7 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -146,44 +146,3 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
         """Copy the current text to the clipboard."""
         self.clipboard_clear()
         self.clipboard_append(self['text'])
-
-
-def openurl(url: str) -> None:
-    r"""
-    Open the given URL in appropriate browser.
-
-    2022-12-06:
-    Firefox itself will gladly attempt to use very long URLs in its URL
-    input.  Up to 16384 was attempted, but the Apache instance this was
-    tested against only allowed up to 8207 total URL length to pass, that
-    being 8190 octets of REQUEST_URI (path + GET params).
-
-    Testing from Windows 10 Home 21H2 cmd.exe with:
-
-        "<path to>\firefox.exe" -osint -url "<test url>"
-
-    only allowed 8115 octest of REQUEST_URI to pass through.
-
-    Microsoft Edge yielded 8092 octets.  Google Chrome yielded 8093 octets.
-
-    However, this is actually the limit of how long a CMD.EXE command-line
-    can be.  The URL was being cut off *there*.
-
-    The 8207 octet URL makes it through `webbrowser.open(<url>)` to:
-
-        Firefox 107.0.1
-        Microsoft Edge 108.0.1462.42
-        Google Chrome 108.0.5359.95
-
-    This was also tested as working *with* the old winreg/subprocess code,
-    so it wasn't even suffering from the same limit as CMD.EXE.
-
-    Conclusion: No reason to not just use `webbrowser.open()`, as prior
-    to e280d6c2833c25867b8139490e68ddf056477917 there was a bug, introduced
-    in 5989acd0d3263e54429ff99769ff73a20476d863, which meant the code always
-    ended up using `webbrowser.open()` *anyway*.
-    :param url: URL to open.
-    """
-    warnings.warn("This function is deprecated. "
-                  "Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2)
-    webbrowser.open(url)

From 57b6ecd88ea9bd989518ddc676db13753ba74973 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:16:45 -0400
Subject: [PATCH 07/49] [2186] Remove Config and Hotkey

---
 config/__init__.py |   3 -
 config/darwin.py   | 191 -------------------------------
 hotkey/__init__.py |   4 -
 hotkey/darwin.py   | 276 ---------------------------------------------
 4 files changed, 474 deletions(-)
 delete mode 100644 config/darwin.py
 delete mode 100644 hotkey/darwin.py

diff --git a/config/__init__.py b/config/__init__.py
index 3acb2edd..992710a0 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -468,9 +468,6 @@ def get_config(*args, **kwargs) -> AbstractConfig:
     :param kwargs: Args to be passed through to implementation.
     :return: Instance of the implementation.
     """
-    if sys.platform == "darwin":  # pragma: sys-platform-darwin
-        from .darwin import MacConfig
-        return MacConfig(*args, **kwargs)
 
     if sys.platform == "win32":  # pragma: sys-platform-win32
         from .windows import WinConfig
diff --git a/config/darwin.py b/config/darwin.py
deleted file mode 100644
index 9c15ec32..00000000
--- a/config/darwin.py
+++ /dev/null
@@ -1,191 +0,0 @@
-"""
-darwin.py - Darwin/macOS implementation of AbstractConfig.
-
-Copyright (c) EDCD, All Rights Reserved
-Licensed under the GNU General Public License.
-See LICENSE file.
-"""
-from __future__ import annotations
-
-import pathlib
-import sys
-from typing import Any
-from Foundation import (  # type: ignore
-    NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults,
-    NSUserDomainMask
-)
-from config import AbstractConfig, appname, logger
-
-assert sys.platform == 'darwin'
-
-
-class MacConfig(AbstractConfig):
-    """MacConfig is the implementation of AbstractConfig for Darwin based OSes."""
-
-    def __init__(self) -> None:
-        super().__init__()
-        support_path = pathlib.Path(
-            NSSearchPathForDirectoriesInDomains(
-                NSApplicationSupportDirectory, NSUserDomainMask, True
-            )[0]
-        )
-
-        self.app_dir_path = support_path / appname
-        self.app_dir_path.mkdir(exist_ok=True)
-
-        self.plugin_dir_path = self.app_dir_path / 'plugins'
-        self.plugin_dir_path.mkdir(exist_ok=True)
-
-        # Bundle IDs identify a singled app though out a system
-
-        if getattr(sys, 'frozen', False):
-            exe_dir = pathlib.Path(sys.executable).parent
-            self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins'
-            self.respath_path = exe_dir.parent / 'Resources'
-            self.identifier = NSBundle.mainBundle().bundleIdentifier()
-
-        else:
-            file_dir = pathlib.Path(__file__).parent.parent
-            self.internal_plugin_dir_path = file_dir / 'plugins'
-            self.respath_path = file_dir
-
-            self.identifier = f'uk.org.marginal.{appname.lower()}'
-            NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
-
-        self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous'
-        self._defaults: Any = NSUserDefaults.standardUserDefaults()
-        self._settings: dict[str, int | str | list] = dict(
-            self._defaults.persistentDomainForName_(self.identifier) or {}
-        )  # make writeable
-
-        if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists():
-            self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
-
-    def __raw_get(self, key: str) -> None | list | str | int:
-        """
-        Retrieve the raw data for the given key.
-
-        :param str: str - The key data is being requested for.
-        :return: The requested data.
-        """
-        res = self._settings.get(key)
-        # On MacOS Catalina, with python.org python 3.9.2 any 'list'
-        # has type __NSCFArray so a simple `isinstance(res, list)` is
-        # False.  So, check it's not-None, and not the other types.
-        #
-        # If we can find where to import the definition of NSCFArray
-        # then we could possibly test against that.
-        if res is not None and not isinstance(res, str) and not isinstance(res, int):
-            return list(res)
-
-        return res
-
-    def get_str(self, key: str, *, default: str = None) -> str:
-        """
-        Return the string referred to by the given key if it exists, or the default.
-
-        Implements :meth:`AbstractConfig.get_str`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-        if not isinstance(res, str):
-            raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}')
-
-        return res
-
-    def get_list(self, key: str, *, default: list = None) -> list:
-        """
-        Return the list referred to by the given key if it exists, or the default.
-
-        Implements :meth:`AbstractConfig.get_list`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-        if not isinstance(res, list):
-            raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
-
-        return res
-
-    def get_int(self, key: str, *, default: int = 0) -> int:
-        """
-        Return the int referred to by key if it exists in the config.
-
-        Implements :meth:`AbstractConfig.get_int`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default
-
-        if not isinstance(res, (str, int)):
-            raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
-
-        try:
-            return int(res)
-
-        except ValueError as e:
-            logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}')
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-    def get_bool(self, key: str, *, default: bool = None) -> bool:
-        """
-        Return the bool referred to by the given key if it exists, or the default.
-
-        Implements :meth:`AbstractConfig.get_bool`.
-        """
-        res = self.__raw_get(key)
-        if res is None:
-            return default  # Yes it could be None, but we're _assuming_ that people gave us a default
-
-        if not isinstance(res, bool):
-            raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
-
-        return res
-
-    def set(self, key: str, val: int | str | list[str] | bool) -> None:
-        """
-        Set the given key's data to the given value.
-
-        Implements :meth:`AbstractConfig.set`.
-        """
-        if self._settings is None:
-            raise ValueError('attempt to use a closed _settings')
-
-        if not isinstance(val, (bool, str, int, list)):
-            raise ValueError(f'Unexpected type for value {type(val)=}')
-
-        self._settings[key] = val
-
-    def delete(self, key: str, *, suppress=False) -> None:
-        """
-        Delete the given key from the config.
-
-        Implements :meth:`AbstractConfig.delete`.
-        """
-        try:
-            del self._settings[key]
-
-        except Exception:
-            if suppress:
-                pass
-
-    def save(self) -> None:
-        """
-        Save the current configuration.
-
-        Implements :meth:`AbstractConfig.save`.
-        """
-        self._defaults.setPersistentDomain_forName_(self._settings, self.identifier)
-        self._defaults.synchronize()
-
-    def close(self) -> None:
-        """
-        Close this config and release any associated resources.
-
-        Implements :meth:`AbstractConfig.close`.
-        """
-        self.save()
-        self._defaults = None
diff --git a/hotkey/__init__.py b/hotkey/__init__.py
index e7515807..313415f9 100644
--- a/hotkey/__init__.py
+++ b/hotkey/__init__.py
@@ -76,10 +76,6 @@ def get_hotkeymgr() -> AbstractHotkeyMgr:
     :return: Appropriate class instance.
     :raises ValueError: If unsupported platform.
     """
-    if sys.platform == 'darwin':
-        from hotkey.darwin import MacHotkeyMgr
-        return MacHotkeyMgr()
-
     if sys.platform == 'win32':
         from hotkey.windows import WindowsHotkeyMgr
         return WindowsHotkeyMgr()
diff --git a/hotkey/darwin.py b/hotkey/darwin.py
deleted file mode 100644
index 6afd0239..00000000
--- a/hotkey/darwin.py
+++ /dev/null
@@ -1,276 +0,0 @@
-"""darwin/macOS implementation of hotkey.AbstractHotkeyMgr."""
-from __future__ import annotations
-
-import pathlib
-import sys
-import tkinter as tk
-from typing import Callable
-assert sys.platform == 'darwin'
-
-import objc
-from AppKit import (
-    NSAlternateKeyMask, NSApplication, NSBeep, NSClearLineFunctionKey, NSCommandKeyMask, NSControlKeyMask,
-    NSDeleteFunctionKey, NSDeviceIndependentModifierFlagsMask, NSEvent, NSF1FunctionKey, NSF35FunctionKey,
-    NSFlagsChanged, NSKeyDown, NSKeyDownMask, NSKeyUp, NSNumericPadKeyMask, NSShiftKeyMask, NSSound, NSWorkspace
-)
-
-from config import config
-from EDMCLogging import get_main_logger
-from hotkey import AbstractHotkeyMgr
-
-logger = get_main_logger()
-
-
-class MacHotkeyMgr(AbstractHotkeyMgr):
-    """Hot key management."""
-
-    POLL = 250
-    # https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSEvent_Class/#//apple_ref/doc/constant_group/Function_Key_Unicodes
-    DISPLAY = {
-        0x03: u'⌅', 0x09: u'⇥', 0xd: u'↩', 0x19: u'⇤', 0x1b: u'esc', 0x20: u'⏘', 0x7f: u'⌫',
-        0xf700: u'↑', 0xf701: u'↓', 0xf702: u'←', 0xf703: u'→',
-        0xf727: u'Ins',
-        0xf728: u'⌦', 0xf729: u'↖', 0xf72a: u'Fn', 0xf72b: u'↘',
-        0xf72c: u'⇞', 0xf72d: u'⇟', 0xf72e: u'PrtScr', 0xf72f: u'ScrollLock',
-        0xf730: u'Pause', 0xf731: u'SysReq', 0xf732: u'Break', 0xf733: u'Reset',
-        0xf739: u'⌧',
-    }
-    (ACQUIRE_INACTIVE, ACQUIRE_ACTIVE, ACQUIRE_NEW) = range(3)
-
-    def __init__(self):
-        self.MODIFIERMASK = NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask \
-            | NSNumericPadKeyMask
-        self.root: tk.Tk
-
-        self.keycode = 0
-        self.modifiers = 0
-        self.activated = False
-        self.observer = None
-
-        self.acquire_key = 0
-        self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-
-        self.tkProcessKeyEvent_old: Callable
-
-        self.snd_good = NSSound.alloc().initWithContentsOfFile_byReference_(
-            pathlib.Path(config.respath_path) / 'snd_good.wav', False
-        )
-        self.snd_bad = NSSound.alloc().initWithContentsOfFile_byReference_(
-            pathlib.Path(config.respath_path) / 'snd_bad.wav', False
-        )
-
-    def register(self, root: tk.Tk, keycode: int, modifiers: int) -> None:
-        """
-        Register current hotkey for monitoring.
-
-        :param root: parent window.
-        :param keycode: Key to monitor.
-        :param modifiers: Any modifiers to take into account.
-        """
-        self.root = root
-        self.keycode = keycode
-        self.modifiers = modifiers
-        self.activated = False
-
-        if keycode:
-            if not self.observer:
-                self.root.after_idle(self._observe)
-            self.root.after(MacHotkeyMgr.POLL, self._poll)
-
-        # Monkey-patch tk (tkMacOSXKeyEvent.c)
-        if not callable(self.tkProcessKeyEvent_old):
-            sel = b'tkProcessKeyEvent:'
-            cls = NSApplication.sharedApplication().class__()  # type: ignore
-            self.tkProcessKeyEvent_old = NSApplication.sharedApplication().methodForSelector_(sel)  # type: ignore
-            newmethod = objc.selector(  # type: ignore
-                self.tkProcessKeyEvent,
-                selector=self.tkProcessKeyEvent_old.selector,
-                signature=self.tkProcessKeyEvent_old.signature
-            )
-            objc.classAddMethod(cls, sel, newmethod)  # type: ignore
-
-    def tkProcessKeyEvent(self, cls, the_event):  # noqa: N802
-        """
-        Monkey-patch tk (tkMacOSXKeyEvent.c).
-
-        - workaround crash on OSX 10.9 & 10.10 on seeing a composing character
-        - notice when modifier key state changes
-        - keep a copy of NSEvent.charactersIgnoringModifiers, which is what we need for the hotkey
-
-        (Would like to use a decorator but need to ensure the application is created before this is installed)
-        :param cls: ???
-        :param the_event: tk event
-        :return: ???
-        """
-        if self.acquire_state:
-            if the_event.type() == NSFlagsChanged:
-                self.acquire_key = the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
-                # suppress the event by not chaining the old function
-                return the_event
-
-            if the_event.type() in (NSKeyDown, NSKeyUp):
-                c = the_event.charactersIgnoringModifiers()
-                self.acquire_key = (c and ord(c[0]) or 0) | \
-                                   (the_event.modifierFlags() & NSDeviceIndependentModifierFlagsMask)
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_NEW
-                # suppress the event by not chaining the old function
-                return the_event
-
-        # replace empty characters with charactersIgnoringModifiers to avoid crash
-        elif the_event.type() in (NSKeyDown, NSKeyUp) and not the_event.characters():
-            the_event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(  # noqa: E501
-                # noqa: E501
-                the_event.type(),
-                the_event.locationInWindow(),
-                the_event.modifierFlags(),
-                the_event.timestamp(),
-                the_event.windowNumber(),
-                the_event.context(),
-                the_event.charactersIgnoringModifiers(),
-                the_event.charactersIgnoringModifiers(),
-                the_event.isARepeat(),
-                the_event.keyCode()
-            )
-        return self.tkProcessKeyEvent_old(cls, the_event)
-
-    def _observe(self):
-        # Must be called after root.mainloop() so that the app's message loop has been created
-        self.observer = NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, self._handler)
-
-    def _poll(self):
-        if config.shutting_down:
-            return
-
-        # No way of signalling to Tkinter from within the callback handler block that doesn't
-        # cause Python to crash, so poll.
-        if self.activated:
-            self.activated = False
-            self.root.event_generate('<<Invoke>>', when="tail")
-
-        if self.keycode or self.modifiers:
-            self.root.after(MacHotkeyMgr.POLL, self._poll)
-
-    def unregister(self) -> None:
-        """Remove hotkey registration."""
-        self.keycode = 0
-        self.modifiers = 0
-
-    @objc.callbackFor(NSEvent.addGlobalMonitorForEventsMatchingMask_handler_)
-    def _handler(self, event) -> None:
-        # use event.charactersIgnoringModifiers to handle composing characters like Alt-e
-        if (
-            (event.modifierFlags() & self.MODIFIERMASK) == self.modifiers
-            and ord(event.charactersIgnoringModifiers()[0]) == self.keycode
-        ):
-            if config.get_int('hotkey_always'):
-                self.activated = True
-
-            else:  # Only trigger if game client is front process
-                front = NSWorkspace.sharedWorkspace().frontmostApplication()
-                if front and front.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
-                    self.activated = True
-
-    def acquire_start(self) -> None:
-        """Start acquiring hotkey state via polling."""
-        self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
-        self.root.after_idle(self._acquire_poll)
-
-    def acquire_stop(self) -> None:
-        """Stop acquiring hotkey state."""
-        self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-
-    def _acquire_poll(self) -> None:
-        """Perform a poll of current hotkey state."""
-        if config.shutting_down:
-            return
-
-        # No way of signalling to Tkinter from within the monkey-patched event handler that doesn't
-        # cause Python to crash, so poll.
-        if self.acquire_state:
-            if self.acquire_state == MacHotkeyMgr.ACQUIRE_NEW:
-                # Abuse tkEvent's keycode field to hold our acquired key & modifier
-                self.root.event_generate('<KeyPress>', keycode=self.acquire_key)
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_ACTIVE
-            self.root.after(50, self._acquire_poll)
-
-    def fromevent(self, event) -> bool | tuple | None:
-        """
-        Return configuration (keycode, modifiers) or None=clear or False=retain previous.
-
-        :param event: tk event ?
-        :return: False to retain previous, None to not use, else (keycode, modifiers)
-        """
-        (keycode, modifiers) = (event.keycode & 0xffff, event.keycode & 0xffff0000)  # Set by _acquire_poll()
-        if (
-            keycode
-            and not (modifiers & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask))
-        ):
-            if keycode == 0x1b:  # Esc = retain previous
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-                return False
-
-            # BkSp, Del, Clear = clear hotkey
-            if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)):
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-                return None
-
-            # don't allow keys needed for typing in System Map
-            if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a:
-                NSBeep()
-                self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
-                return None
-
-        return keycode, modifiers
-
-    def display(self, keycode, modifiers) -> str:
-        """
-        Return displayable form of given hotkey + modifiers.
-
-        :param keycode:
-        :param modifiers:
-        :return: string form
-        """
-        text = ''
-        if modifiers & NSControlKeyMask:
-            text += u'⌃'
-
-        if modifiers & NSAlternateKeyMask:
-            text += u'⌥'
-
-        if modifiers & NSShiftKeyMask:
-            text += u'⇧'
-
-        if modifiers & NSCommandKeyMask:
-            text += u'⌘'
-
-        if (modifiers & NSNumericPadKeyMask) and keycode <= 0x7f:
-            text += u'№'
-
-        if not keycode:
-            pass
-
-        elif ord(NSF1FunctionKey) <= keycode <= ord(NSF35FunctionKey):
-            text += f'F{keycode + 1 - ord(NSF1FunctionKey)}'
-
-        elif keycode in MacHotkeyMgr.DISPLAY:  # specials
-            text += MacHotkeyMgr.DISPLAY[keycode]
-
-        elif keycode < 0x20:  # control keys
-            text += chr(keycode + 0x40)
-
-        elif keycode < 0xf700:  # key char
-            text += chr(keycode).upper()
-
-        else:
-            text += u'⁈'
-
-        return text
-
-    def play_good(self):
-        """Play the 'good' sound."""
-        self.snd_good.play()
-
-    def play_bad(self):
-        """Play the 'bad' sound."""
-        self.snd_bad.play()

From 93d26e07e057d1b70f3144898ecf34e35c4cdae2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:19:15 -0400
Subject: [PATCH 08/49] [2186] Remove Monitor, L10n References

---
 l10n.py    | 50 +++++---------------------------------------------
 monitor.py | 23 ++---------------------
 2 files changed, 7 insertions(+), 66 deletions(-)

diff --git a/l10n.py b/l10n.py
index a2b5185a..183d902a 100755
--- a/l10n.py
+++ b/l10n.py
@@ -17,8 +17,8 @@ import re
 import sys
 import warnings
 from contextlib import suppress
-from os import pardir, listdir, sep, makedirs
-from os.path import basename, dirname, isdir, isfile, join, abspath, exists
+from os import listdir, sep, makedirs
+from os.path import basename, dirname, isdir, join, abspath, exists
 from typing import TYPE_CHECKING, Iterable, TextIO, cast
 from config import config
 from EDMCLogging import get_main_logger
@@ -39,12 +39,7 @@ logger = get_main_logger()
 LANGUAGE_ID = '!Language'
 LOCALISATION_DIR = 'L10n'
 
-if sys.platform == 'darwin':
-    from Foundation import (  # type: ignore # exists on Darwin
-        NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle
-    )
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR
     if TYPE_CHECKING:
@@ -178,14 +173,8 @@ class _Translations:
     def available(self) -> set[str]:
         """Return a list of available language codes."""
         path = self.respath()
-        if getattr(sys, 'frozen', False) and sys.platform == 'darwin':
-            available = {
-                x[:-len('.lproj')] for x in listdir(path)
-                if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))
-            }
 
-        else:
-            available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
+        available = {x[:-len('.strings')] for x in listdir(path) if x.endswith('.strings')}
 
         return available
 
@@ -206,9 +195,6 @@ class _Translations:
     def respath(self) -> str:
         """Path to localisation files."""
         if getattr(sys, 'frozen', False):
-            if sys.platform == 'darwin':
-                return abspath(join(dirname(sys.executable), pardir, 'Resources'))
-
             return abspath(join(dirname(sys.executable), LOCALISATION_DIR))
 
         if __file__:
@@ -234,10 +220,6 @@ class _Translations:
             except OSError:
                 logger.exception(f'could not open {file_path}')
 
-        elif getattr(sys, 'frozen', False) and sys.platform == 'darwin':
-            res_path = join(self.respath(), f'{lang}.lproj', 'Localizable.strings')
-            return open(res_path, encoding='utf-16')
-
         res_path = join(self.respath(), f'{lang}.strings')
         return open(res_path, encoding='utf-8')
 
@@ -245,15 +227,6 @@ class _Translations:
 class _Locale:
     """Locale holds a few utility methods to convert data to and from localized versions."""
 
-    def __init__(self) -> None:
-        if sys.platform == 'darwin':
-            self.int_formatter = NSNumberFormatter.alloc().init()
-            self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
-            self.float_formatter = NSNumberFormatter.alloc().init()
-            self.float_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle)
-            self.float_formatter.setMinimumFractionDigits_(5)
-            self.float_formatter.setMaximumFractionDigits_(5)
-
     def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str:  # noqa: N802
         warnings.warn(DeprecationWarning('use _Locale.string_from_number instead.'))
         return self.string_from_number(number, decimals)  # type: ignore
@@ -279,14 +252,6 @@ class _Locale:
         if decimals == 0 and not isinstance(number, numbers.Integral):
             number = int(round(number))
 
-        if sys.platform == 'darwin':
-            if not decimals and isinstance(number, numbers.Integral):
-                return self.int_formatter.stringFromNumber_(number)
-
-            self.float_formatter.setMinimumFractionDigits_(decimals)
-            self.float_formatter.setMaximumFractionDigits_(decimals)
-            return self.float_formatter.stringFromNumber_(number)
-
         if not decimals and isinstance(number, numbers.Integral):
             return locale.format_string('%d', number, True)
         return locale.format_string('%.*f', (decimals, number), True)
@@ -299,9 +264,6 @@ class _Locale:
         :param string: The string to convert
         :return: None if the string cannot be parsed, otherwise an int or float dependant on input data.
         """
-        if sys.platform == 'darwin':
-            return self.float_formatter.numberFromString_(string)
-
         with suppress(ValueError):
             return locale.atoi(string)
 
@@ -332,10 +294,8 @@ class _Locale:
         :return: The preferred language list
         """
         languages: Iterable[str]
-        if sys.platform == 'darwin':
-            languages = NSLocale.preferredLanguages()
 
-        elif sys.platform != 'win32':
+        if sys.platform != 'win32':
             # POSIX
             lang = locale.getlocale()[0]
             languages = [lang.replace('_', '-')] if lang else []
diff --git a/monitor.py b/monitor.py
index 8364f95b..d549e533 100644
--- a/monitor.py
+++ b/monitor.py
@@ -38,16 +38,7 @@ if TYPE_CHECKING:
     def _(x: str) -> str:
         return x
 
-if sys.platform == 'darwin':
-    from fcntl import fcntl
-
-    from AppKit import NSWorkspace
-    from watchdog.events import FileSystemEventHandler
-    from watchdog.observers import Observer
-    from watchdog.observers.api import BaseObserver
-    F_GLOBAL_NOCACHE = 55
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
 
@@ -382,8 +373,6 @@ class EDLogs(FileSystemEventHandler):
         logfile = self.logfile
         if logfile:
             loghandle: BinaryIO = open(logfile, 'rb', 0)  # unbuffered
-            if sys.platform == 'darwin':
-                fcntl(loghandle, F_GLOBAL_NOCACHE, -1)  # required to avoid corruption on macOS over SMB
 
             self.catching_up = True
             for line in loghandle:
@@ -483,9 +472,6 @@ class EDLogs(FileSystemEventHandler):
 
                 if logfile:
                     loghandle = open(logfile, 'rb', 0)  # unbuffered
-                    if sys.platform == 'darwin':
-                        fcntl(loghandle, F_GLOBAL_NOCACHE, -1)  # required to avoid corruption on macOS over SMB
-
                     log_pos = 0
 
             sleep(self._POLL)
@@ -2144,12 +2130,7 @@ class EDLogs(FileSystemEventHandler):
 
         :return: bool - True if the game is running.
         """
-        if sys.platform == 'darwin':
-            for app in NSWorkspace.sharedWorkspace().runningApplications():
-                if app.bundleIdentifier() == 'uk.co.frontier.EliteDangerous':
-                    return True
-
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             def WindowTitle(h):  # noqa: N802
                 if h:
                     length = GetWindowTextLength(h) + 1

From 27093d88620ed760600b0e1df672160e25dffc7c Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:26:25 -0400
Subject: [PATCH 09/49] [2186] Main, Dashboard, Prefs

---
 EDMarketConnector.py | 342 ++++++++++++++++---------------------------
 dashboard.py         |   2 +-
 myNotebook.py        |   2 +-
 prefs.py             | 177 ++++------------------
 4 files changed, 161 insertions(+), 362 deletions(-)

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index 6debf4e9..967488e0 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -496,21 +496,20 @@ class AppWindow:
 
         plug.load_plugins(master)
 
-        if sys.platform != 'darwin':
-            if sys.platform == 'win32':
-                self.w.wm_iconbitmap(default='EDMarketConnector.ico')
+        if sys.platform == 'win32':
+            self.w.wm_iconbitmap(default='EDMarketConnector.ico')
 
-            else:
-                self.w.tk.call('wm', 'iconphoto', self.w, '-default',
-                               tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
+        else:
+            self.w.tk.call('wm', 'iconphoto', self.w, '-default',
+                           tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png')))
 
-            # TODO: Export to files and merge from them in future ?
-            self.theme_icon = tk.PhotoImage(
-                data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==')  # noqa: E501
-            self.theme_minimize = tk.BitmapImage(
-                data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n   0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
-            self.theme_close = tk.BitmapImage(
-                data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n   0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n   0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
+        # TODO: Export to files and merge from them in future ?
+        self.theme_icon = tk.PhotoImage(
+            data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==')  # noqa: E501
+        self.theme_minimize = tk.BitmapImage(
+            data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n   0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
+        self.theme_close = tk.BitmapImage(
+            data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n   0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n   0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n   0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n')  # noqa: E501
 
         frame = tk.Frame(self.w, name=appname.lower())
         frame.grid(sticky=tk.NSEW)
@@ -599,7 +598,7 @@ class AppWindow:
         self.theme_button = tk.Label(
             frame,
             name='themed_update_button',
-            width=32 if sys.platform == 'darwin' else 28,
+            width=28,
             state=tk.DISABLED
         )
 
@@ -633,148 +632,104 @@ class AppWindow:
             self.updater = update.Updater(tkroot=self.w)
             self.updater.check_for_updates()  # Sparkle / WinSparkle does this automatically for packaged apps
 
-        if sys.platform == 'darwin':
-            # Can't handle (de)iconify if topmost is set, so suppress iconify button
-            # http://wiki.tcl.tk/13428 and p15 of
-            # https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf
-            root.call('tk::unsupported::MacWindowStyle', 'style', root, 'document', 'closeBox resizable')
+        self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
+        self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
+        self.file_menu.add_command(command=self.save_raw)
+        self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
+        self.file_menu.add_separator()
+        self.file_menu.add_command(command=self.onexit)
+        self.menubar.add_cascade(menu=self.file_menu)
+        self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
+        self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
+        self.menubar.add_cascade(menu=self.edit_menu)
+        self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)  # type: ignore
+        self.help_menu.add_command(command=self.help_general)  # Documentation
+        self.help_menu.add_command(command=self.help_troubleshooting)  # Troubleshooting
+        self.help_menu.add_command(command=self.help_report_a_bug)  # Report A Bug
+        self.help_menu.add_command(command=self.help_privacy)  # Privacy Policy
+        self.help_menu.add_command(command=self.help_releases)  # Release Notes
+        self.help_menu.add_command(command=lambda: self.updater.check_for_updates())  # Check for Updates...
+        # About E:D Market Connector
+        self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
+        self.help_menu.add_command(command=prefs.help_open_log_folder)  # Open Log Folder
 
-            # https://www.tcl.tk/man/tcl/TkCmd/menu.htm
-            self.system_menu = tk.Menu(self.menubar, name='apple')
-            self.system_menu.add_command(command=lambda: self.w.call('tk::mac::standardAboutPanel'))
-            self.system_menu.add_command(command=lambda: self.updater.check_for_updates())
+        self.menubar.add_cascade(menu=self.help_menu)
+        if sys.platform == 'win32':
+            # Must be added after at least one "real" menu entry
+            self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
+            self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
+            self.system_menu.add_separator()
+            # LANG: Appearance - Label for checkbox to select if application always on top
+            self.system_menu.add_checkbutton(label=_('Always on top'),
+                                             variable=self.always_ontop,
+                                             command=self.ontop_changed)  # Appearance setting
             self.menubar.add_cascade(menu=self.system_menu)
-            self.file_menu = tk.Menu(self.menubar, name='file')
-            self.file_menu.add_command(command=self.save_raw)
-            self.menubar.add_cascade(menu=self.file_menu)
-            self.edit_menu = tk.Menu(self.menubar, name='edit')
-            self.edit_menu.add_command(accelerator='Command-c', state=tk.DISABLED, command=self.copy)
-            self.menubar.add_cascade(menu=self.edit_menu)
-            self.w.bind('<Command-c>', self.copy)
-            self.view_menu = tk.Menu(self.menubar, name='view')
-            self.view_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
-            self.menubar.add_cascade(menu=self.view_menu)
-            window_menu = tk.Menu(self.menubar, name='window')
-            self.menubar.add_cascade(menu=window_menu)
-            self.help_menu = tk.Menu(self.menubar, name='help')
-            self.w.createcommand("::tk::mac::ShowHelp", self.help_general)
-            self.help_menu.add_command(command=self.help_troubleshooting)
-            self.help_menu.add_command(command=self.help_report_a_bug)
-            self.help_menu.add_command(command=self.help_privacy)
-            self.help_menu.add_command(command=self.help_releases)
-            self.menubar.add_cascade(menu=self.help_menu)
-            self.w['menu'] = self.menubar
-            # https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm
-            self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0')
-            self.w.createcommand('tkAboutDialog', lambda: self.w.call('tk::mac::standardAboutPanel'))
-            self.w.createcommand("::tk::mac::Quit", self.onexit)
-            self.w.createcommand("::tk::mac::ShowPreferences", lambda: prefs.PreferencesDialog(self.w, self.postprefs))
-            self.w.createcommand("::tk::mac::ReopenApplication", self.w.deiconify)  # click on app in dock = restore
-            self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw)  # close button shouldn't quit app
-            self.w.resizable(tk.FALSE, tk.FALSE)  # Can't be only resizable on one axis
-        else:
-            self.file_menu = self.view_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
-            self.file_menu.add_command(command=lambda: stats.StatsDialog(self.w, self.status))
-            self.file_menu.add_command(command=self.save_raw)
-            self.file_menu.add_command(command=lambda: prefs.PreferencesDialog(self.w, self.postprefs))
-            self.file_menu.add_separator()
-            self.file_menu.add_command(command=self.onexit)
-            self.menubar.add_cascade(menu=self.file_menu)
-            self.edit_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)
-            self.edit_menu.add_command(accelerator='Ctrl+C', state=tk.DISABLED, command=self.copy)
-            self.menubar.add_cascade(menu=self.edit_menu)
-            self.help_menu = tk.Menu(self.menubar, tearoff=tk.FALSE)  # type: ignore
-            self.help_menu.add_command(command=self.help_general)  # Documentation
-            self.help_menu.add_command(command=self.help_troubleshooting)  # Troubleshooting
-            self.help_menu.add_command(command=self.help_report_a_bug)  # Report A Bug
-            self.help_menu.add_command(command=self.help_privacy)  # Privacy Policy
-            self.help_menu.add_command(command=self.help_releases)  # Release Notes
-            self.help_menu.add_command(command=lambda: self.updater.check_for_updates())  # Check for Updates...
-            # About E:D Market Connector
-            self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
-            self.help_menu.add_command(command=prefs.help_open_log_folder)  # Open Log Folder
+        self.w.bind('<Control-c>', self.copy)
 
-            self.menubar.add_cascade(menu=self.help_menu)
-            if sys.platform == 'win32':
-                # Must be added after at least one "real" menu entry
-                self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop')))
-                self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE)
-                self.system_menu.add_separator()
-                # LANG: Appearance - Label for checkbox to select if application always on top
-                self.system_menu.add_checkbutton(label=_('Always on top'),
-                                                 variable=self.always_ontop,
-                                                 command=self.ontop_changed)  # Appearance setting
-                self.menubar.add_cascade(menu=self.system_menu)
-            self.w.bind('<Control-c>', self.copy)
+        # Bind to the Default theme minimise button
+        self.w.bind("<Unmap>", self.default_iconify)
 
-            # Bind to the Default theme minimise button
-            self.w.bind("<Unmap>", self.default_iconify)
+        self.w.protocol("WM_DELETE_WINDOW", self.onexit)
+        theme.register(self.menubar)  # menus and children aren't automatically registered
+        theme.register(self.file_menu)
+        theme.register(self.edit_menu)
+        theme.register(self.help_menu)
 
-            self.w.protocol("WM_DELETE_WINDOW", self.onexit)
-            theme.register(self.menubar)  # menus and children aren't automatically registered
-            theme.register(self.file_menu)
-            theme.register(self.edit_menu)
-            theme.register(self.help_menu)
-
-            # Alternate title bar and menu for dark theme
-            self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
-            self.theme_menubar.columnconfigure(2, weight=1)
-            theme_titlebar = tk.Label(
-                self.theme_menubar,
-                name="alternate_titlebar",
-                text=applongname,
-                image=self.theme_icon, cursor='fleur',
-                anchor=tk.W, compound=tk.LEFT
-            )
-            theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
-            self.drag_offset: tuple[int | None, int | None] = (None, None)
-            theme_titlebar.bind('<Button-1>', self.drag_start)
-            theme_titlebar.bind('<B1-Motion>', self.drag_continue)
-            theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
-            theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
-            theme_minimize.grid(row=0, column=3, padx=2)
-            theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
-            theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
-            theme_close.grid(row=0, column=4, padx=2)
-            theme.button_bind(theme_close, self.onexit, image=self.theme_close)
-            self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
-            self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
-            theme.button_bind(self.theme_file_menu,
-                              lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
-                                                                e.widget.winfo_rooty()
-                                                                + e.widget.winfo_height()))
-            self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
-            self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
-            theme.button_bind(self.theme_edit_menu,
-                              lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
-                                                                e.widget.winfo_rooty()
-                                                                + e.widget.winfo_height()))
-            self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
-            self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
-            theme.button_bind(self.theme_help_menu,
-                              lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
-                                                                e.widget.winfo_rooty()
-                                                                + e.widget.winfo_height()))
-            tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
-            theme.register(self.theme_minimize)  # images aren't automatically registered
-            theme.register(self.theme_close)
-            self.blank_menubar = tk.Frame(frame, name="blank_menubar")
-            tk.Label(self.blank_menubar).grid()
-            tk.Label(self.blank_menubar).grid()
-            tk.Frame(self.blank_menubar, height=2).grid()
-            theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
-                                     {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
-            self.w.resizable(tk.TRUE, tk.FALSE)
+        # Alternate title bar and menu for dark theme
+        self.theme_menubar = tk.Frame(frame, name="alternate_menubar")
+        self.theme_menubar.columnconfigure(2, weight=1)
+        theme_titlebar = tk.Label(
+            self.theme_menubar,
+            name="alternate_titlebar",
+            text=applongname,
+            image=self.theme_icon, cursor='fleur',
+            anchor=tk.W, compound=tk.LEFT
+        )
+        theme_titlebar.grid(columnspan=3, padx=2, sticky=tk.NSEW)
+        self.drag_offset: tuple[int | None, int | None] = (None, None)
+        theme_titlebar.bind('<Button-1>', self.drag_start)
+        theme_titlebar.bind('<B1-Motion>', self.drag_continue)
+        theme_titlebar.bind('<ButtonRelease-1>', self.drag_end)
+        theme_minimize = tk.Label(self.theme_menubar, image=self.theme_minimize)
+        theme_minimize.grid(row=0, column=3, padx=2)
+        theme.button_bind(theme_minimize, self.oniconify, image=self.theme_minimize)
+        theme_close = tk.Label(self.theme_menubar, image=self.theme_close)
+        theme_close.grid(row=0, column=4, padx=2)
+        theme.button_bind(theme_close, self.onexit, image=self.theme_close)
+        self.theme_file_menu = tk.Label(self.theme_menubar, anchor=tk.W)
+        self.theme_file_menu.grid(row=1, column=0, padx=self.PADX, sticky=tk.W)
+        theme.button_bind(self.theme_file_menu,
+                          lambda e: self.file_menu.tk_popup(e.widget.winfo_rootx(),
+                                                            e.widget.winfo_rooty()
+                                                            + e.widget.winfo_height()))
+        self.theme_edit_menu = tk.Label(self.theme_menubar, anchor=tk.W)
+        self.theme_edit_menu.grid(row=1, column=1, sticky=tk.W)
+        theme.button_bind(self.theme_edit_menu,
+                          lambda e: self.edit_menu.tk_popup(e.widget.winfo_rootx(),
+                                                            e.widget.winfo_rooty()
+                                                            + e.widget.winfo_height()))
+        self.theme_help_menu = tk.Label(self.theme_menubar, anchor=tk.W)
+        self.theme_help_menu.grid(row=1, column=2, sticky=tk.W)
+        theme.button_bind(self.theme_help_menu,
+                          lambda e: self.help_menu.tk_popup(e.widget.winfo_rootx(),
+                                                            e.widget.winfo_rooty()
+                                                            + e.widget.winfo_height()))
+        tk.Frame(self.theme_menubar, highlightthickness=1).grid(columnspan=5, padx=self.PADX, sticky=tk.EW)
+        theme.register(self.theme_minimize)  # images aren't automatically registered
+        theme.register(self.theme_close)
+        self.blank_menubar = tk.Frame(frame, name="blank_menubar")
+        tk.Label(self.blank_menubar).grid()
+        tk.Label(self.blank_menubar).grid()
+        tk.Frame(self.blank_menubar, height=2).grid()
+        theme.register_alternate((self.menubar, self.theme_menubar, self.blank_menubar),
+                                 {'row': 0, 'columnspan': 2, 'sticky': tk.NSEW})
+        self.w.resizable(tk.TRUE, tk.FALSE)
 
         # update geometry
         if config.get_str('geometry'):
             match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry'))
             if match:
-                if sys.platform == 'darwin':
-                    # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-                    if int(match.group(2)) >= 0:
-                        self.w.geometry(config.get_str('geometry'))
-                elif sys.platform == 'win32':
+                if sys.platform == 'win32':
                     # Check that the titlebar will be at least partly on screen
                     import ctypes
                     from ctypes.wintypes import POINT
@@ -910,49 +865,28 @@ class AppWindow:
         self.system_label['text'] = _('System') + ':'  # LANG: Label for 'System' line in main UI
         self.station_label['text'] = _('Station') + ':'  # LANG: Label for 'Station' line in main UI
         self.button['text'] = self.theme_button['text'] = _('Update')  # LANG: Update button in main window
-        if sys.platform == 'darwin':
-            self.menubar.entryconfigure(1, label=_('File'))  # LANG: 'File' menu title on OSX
-            self.menubar.entryconfigure(2, label=_('Edit'))  # LANG: 'Edit' menu title on OSX
-            self.menubar.entryconfigure(3, label=_('View'))  # LANG: 'View' menu title on OSX
-            self.menubar.entryconfigure(4, label=_('Window'))  # LANG: 'Window' menu title on OSX
-            self.menubar.entryconfigure(5, label=_('Help'))  # LANG: Help' menu title on OSX
-            self.system_menu.entryconfigure(
-                0,
-                label=_("About {APP}").format(APP=applongname)  # LANG: App menu entry on OSX
-            )
-            self.system_menu.entryconfigure(1, label=_("Check for Updates..."))  # LANG: Help > Check for Updates...
-            self.file_menu.entryconfigure(0, label=_('Save Raw Data...'))  # LANG: File > Save Raw Data...
-            self.view_menu.entryconfigure(0, label=_('Status'))  # LANG: File > Status
-            self.help_menu.entryconfigure(1, label=_('Documentation'))  # LANG: Help > Documentation
-            self.help_menu.entryconfigure(2, label=_('Troubleshooting'))  # LANG: Help > Troubleshooting
-            self.help_menu.entryconfigure(3, label=_('Report A Bug'))  # LANG: Help > Report A Bug
-            self.help_menu.entryconfigure(4, label=_('Privacy Policy'))  # LANG: Help > Privacy Policy
-            self.help_menu.entryconfigure(5, label=_('Release Notes'))  # LANG: Help > Release Notes
-            self.help_menu.entryconfigure(6, label=_('Open Log Folder'))  # LANG: Help > Open Log Folder
+        self.menubar.entryconfigure(1, label=_('File'))  # LANG: 'File' menu title
+        self.menubar.entryconfigure(2, label=_('Edit'))  # LANG: 'Edit' menu title
+        self.menubar.entryconfigure(3, label=_('Help'))  # LANG: 'Help' menu title
+        self.theme_file_menu['text'] = _('File')  # LANG: 'File' menu title
+        self.theme_edit_menu['text'] = _('Edit')  # LANG: 'Edit' menu title
+        self.theme_help_menu['text'] = _('Help')  # LANG: 'Help' menu title
 
-        else:
-            self.menubar.entryconfigure(1, label=_('File'))  # LANG: 'File' menu title
-            self.menubar.entryconfigure(2, label=_('Edit'))  # LANG: 'Edit' menu title
-            self.menubar.entryconfigure(3, label=_('Help'))  # LANG: 'Help' menu title
-            self.theme_file_menu['text'] = _('File')  # LANG: 'File' menu title
-            self.theme_edit_menu['text'] = _('Edit')  # LANG: 'Edit' menu title
-            self.theme_help_menu['text'] = _('Help')  # LANG: 'Help' menu title
+        # File menu
+        self.file_menu.entryconfigure(0, label=_('Status'))  # LANG: File > Status
+        self.file_menu.entryconfigure(1, label=_('Save Raw Data...'))  # LANG: File > Save Raw Data...
+        self.file_menu.entryconfigure(2, label=_('Settings'))  # LANG: File > Settings
+        self.file_menu.entryconfigure(4, label=_('Exit'))  # LANG: File > Exit
 
-            # File menu
-            self.file_menu.entryconfigure(0, label=_('Status'))  # LANG: File > Status
-            self.file_menu.entryconfigure(1, label=_('Save Raw Data...'))  # LANG: File > Save Raw Data...
-            self.file_menu.entryconfigure(2, label=_('Settings'))  # LANG: File > Settings
-            self.file_menu.entryconfigure(4, label=_('Exit'))  # LANG: File > Exit
-
-            # Help menu
-            self.help_menu.entryconfigure(0, label=_('Documentation'))  # LANG: Help > Documentation
-            self.help_menu.entryconfigure(1, label=_('Troubleshooting'))  # LANG: Help > Troubleshooting
-            self.help_menu.entryconfigure(2, label=_('Report A Bug'))  # LANG: Help > Report A Bug
-            self.help_menu.entryconfigure(3, label=_('Privacy Policy'))  # LANG: Help > Privacy Policy
-            self.help_menu.entryconfigure(4, label=_('Release Notes'))  # LANG: Help > Release Notes
-            self.help_menu.entryconfigure(5, label=_('Check for Updates...'))  # LANG: Help > Check for Updates...
-            self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname))  # LANG: Help > About App
-            self.help_menu.entryconfigure(7, label=_('Open Log Folder'))  # LANG: Help > Open Log Folder
+        # Help menu
+        self.help_menu.entryconfigure(0, label=_('Documentation'))  # LANG: Help > Documentation
+        self.help_menu.entryconfigure(1, label=_('Troubleshooting'))  # LANG: Help > Troubleshooting
+        self.help_menu.entryconfigure(2, label=_('Report A Bug'))  # LANG: Help > Report A Bug
+        self.help_menu.entryconfigure(3, label=_('Privacy Policy'))  # LANG: Help > Privacy Policy
+        self.help_menu.entryconfigure(4, label=_('Release Notes'))  # LANG: Help > Release Notes
+        self.help_menu.entryconfigure(5, label=_('Check for Updates...'))  # LANG: Help > Check for Updates...
+        self.help_menu.entryconfigure(6, label=_("About {APP}").format(APP=applongname))  # LANG: Help > About App
+        self.help_menu.entryconfigure(7, label=_('Open Log Folder'))  # LANG: Help > Open Log Folder
 
         # Edit menu
         self.edit_menu.entryconfigure(0, label=_('Copy'))  # LANG: Label for 'Copy' as in 'Copy and Paste'
@@ -975,13 +909,8 @@ class AppWindow:
 
         self.button['state'] = self.theme_button['state'] = tk.DISABLED
 
-        if sys.platform == 'darwin':
-            self.view_menu.entryconfigure(0, state=tk.DISABLED)  # Status
-            self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Save Raw Data
-
-        else:
-            self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Status
-            self.file_menu.entryconfigure(1, state=tk.DISABLED)  # Save Raw Data
+        self.file_menu.entryconfigure(0, state=tk.DISABLED)  # Status
+        self.file_menu.entryconfigure(1, state=tk.DISABLED)  # Save Raw Data
 
         self.w.update_idletasks()
         try:
@@ -989,13 +918,8 @@ class AppWindow:
                 # LANG: Successfully authenticated with the Frontier website
                 self.status['text'] = _('Authentication successful')
 
-                if sys.platform == 'darwin':
-                    self.view_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                    self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Save Raw Data
-
-                else:
-                    self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                    self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
+                self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
+                self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
 
         except (companion.CredentialsError, companion.ServerError, companion.ServerLagging) as e:
             self.status['text'] = str(e)
@@ -1666,13 +1590,8 @@ class AppWindow:
             companion.session.auth_callback()
             # LANG: Successfully authenticated with the Frontier website
             self.status['text'] = _('Authentication successful')
-            if sys.platform == 'darwin':
-                self.view_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Save Raw Data
-
-            else:
-                self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
-                self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
+            self.file_menu.entryconfigure(0, state=tk.NORMAL)  # Status
+            self.file_menu.entryconfigure(1, state=tk.NORMAL)  # Save Raw Data
 
         except companion.ServerError as e:
             self.status['text'] = str(e)
@@ -1831,7 +1750,7 @@ class AppWindow:
 
             # position over parent
             # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-            if sys.platform != 'darwin' or parent.winfo_rooty() > 0:
+            if parent.winfo_rooty() > 0:
                 self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
 
             # remove decoration
@@ -1916,9 +1835,6 @@ class AppWindow:
         """
         default_extension: str = ''
 
-        if sys.platform == 'darwin':
-            default_extension = '.json'
-
         timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime())
         f = tkinter.filedialog.asksaveasfilename(
             parent=self.w,
@@ -1954,7 +1870,7 @@ class AppWindow:
         config.set_shutdown()  # Signal we're in shutdown now.
 
         # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-        if sys.platform != 'darwin' or self.w.winfo_rooty() > 0:
+        if self.w.winfo_rooty() > 0:
             x, y = self.w.geometry().split('+')[1:3]  # e.g. '212x170+2881+1267'
             config.set('geometry', f'+{x}+{y}')
 
diff --git a/dashboard.py b/dashboard.py
index 1f95a943..3948c92b 100644
--- a/dashboard.py
+++ b/dashboard.py
@@ -20,7 +20,7 @@ from EDMCLogging import get_main_logger
 
 logger = get_main_logger()
 
-if sys.platform in ('darwin', 'win32'):
+if sys.platform == 'win32':
     from watchdog.events import FileSystemEventHandler
     from watchdog.observers import Observer
 else:
diff --git a/myNotebook.py b/myNotebook.py
index 6fa35774..dbfc9250 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -78,7 +78,7 @@ class Label(tk.Label):
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
         # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
-        if sys.platform in ('darwin', 'win32'):
+        if sys.platform == 'win32':
             kw['foreground'] = kw.pop('foreground', PAGEFG)
             kw['background'] = kw.pop('background', PAGEBG)
         else:
diff --git a/prefs.py b/prefs.py
index 6c2722b6..862bda27 100644
--- a/prefs.py
+++ b/prefs.py
@@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, Type
 
 import myNotebook as nb  # noqa: N813
 import plug
-from config import applongname, appversion_nobuild, config
+from config import appversion_nobuild, config
 from EDMCLogging import edmclogger, get_main_logger
 from constants import appname
 from hotkey import hotkeymgr
@@ -49,9 +49,6 @@ def help_open_log_folder() -> None:
     if sys.platform.startswith('win'):
         # On Windows, use the "start" command to open the folder
         system(f'start "" "{logfile_loc}"')
-    elif sys.platform.startswith('darwin'):
-        # On macOS, use the "open" command to open the folder
-        system(f'open "{logfile_loc}"')
     elif sys.platform.startswith('linux'):
         # On Linux, use the "xdg-open" command to open the folder
         system(f'xdg-open "{logfile_loc}"')
@@ -172,32 +169,7 @@ class AutoInc(contextlib.AbstractContextManager):
         return None
 
 
-if sys.platform == 'darwin':
-    import objc  # type: ignore
-    from Foundation import NSFileManager  # type: ignore
-    try:
-        from ApplicationServices import (  # type: ignore
-            AXIsProcessTrusted, AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt
-        )
-
-    except ImportError:
-        HIServices = objc.loadBundle(
-            'HIServices',
-            globals(),
-            '/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework'
-        )
-
-        objc.loadBundleFunctions(
-            HIServices,
-            globals(),
-            [('AXIsProcessTrusted', 'B'), ('AXIsProcessTrustedWithOptions', 'B@')]
-        )
-
-        objc.loadBundleVariables(HIServices, globals(), [('kAXTrustedCheckOptionPrompt', '@^{__CFString=}')])
-
-    was_accessible_at_launch = AXIsProcessTrusted()  # type: ignore
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     import winreg
     from ctypes.wintypes import HINSTANCE, HWND, LPCWSTR, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT
@@ -251,19 +223,14 @@ class PreferencesDialog(tk.Toplevel):
 
         self.parent = parent
         self.callback = callback
-        if sys.platform == 'darwin':
-            # LANG: File > Preferences menu entry for macOS
-            self.title(_('Preferences'))
-
-        else:
-            # LANG: File > Settings (macOS)
-            self.title(_('Settings'))
+        # LANG: File > Settings (macOS)
+        self.title(_('Settings'))
 
         if parent.winfo_viewable():
             self.transient(parent)
 
         # position over parent
-        if sys.platform != 'darwin' or parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+        if parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
             # TODO this is fixed supposedly.
             self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
 
@@ -271,10 +238,6 @@ class PreferencesDialog(tk.Toplevel):
         if sys.platform == 'win32':
             self.attributes('-toolwindow', tk.TRUE)
 
-        elif sys.platform == 'darwin':
-            # http://wiki.tcl.tk/13428
-            parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
-
         self.resizable(tk.FALSE, tk.FALSE)
 
         self.cmdr: str | bool | None = False  # Note if Cmdr changes in the Journal
@@ -302,19 +265,15 @@ class PreferencesDialog(tk.Toplevel):
         self.__setup_appearance_tab(notebook)
         self.__setup_plugin_tab(notebook)
 
-        if sys.platform == 'darwin':
-            self.protocol("WM_DELETE_WINDOW", self.apply)  # close button applies changes
-
-        else:
-            buttonframe = ttk.Frame(frame)
-            buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
-            buttonframe.columnconfigure(0, weight=1)
-            ttk.Label(buttonframe).grid(row=0, column=0)  # spacer
-            # LANG: 'OK' button on Settings/Preferences window
-            button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
-            button.grid(row=0, column=1, sticky=tk.E)
-            button.bind("<Return>", lambda event: self.apply())
-            self.protocol("WM_DELETE_WINDOW", self._destroy)
+        buttonframe = ttk.Frame(frame)
+        buttonframe.grid(padx=self.PADX, pady=self.PADX, sticky=tk.NSEW)
+        buttonframe.columnconfigure(0, weight=1)
+        ttk.Label(buttonframe).grid(row=0, column=0)  # spacer
+        # LANG: 'OK' button on Settings/Preferences window
+        button = ttk.Button(buttonframe, text=_('OK'), command=self.apply)
+        button.grid(row=0, column=1, sticky=tk.E)
+        button.bind("<Return>", lambda event: self.apply())
+        self.protocol("WM_DELETE_WINDOW", self._destroy)
 
         # FIXME: Why are these being called when *creating* the Settings window?
         # Selectively disable buttons depending on output settings
@@ -405,11 +364,7 @@ class PreferencesDialog(tk.Toplevel):
         self.outdir_entry = nb.Entry(output_frame, takefocus=False)
         self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        if sys.platform == 'darwin':
-            text = (_('Change...'))  # LANG: macOS Preferences - files location selection button
-
-        else:
-            text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
+        text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
 
         self.outbutton = nb.Button(
             output_frame,
@@ -455,11 +410,7 @@ class PreferencesDialog(tk.Toplevel):
 
         self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        if sys.platform == 'darwin':
-            text = (_('Change...'))  # LANG: macOS Preferences - files location selection button
-
-        else:
-            text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
+        text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
 
         with row as cur_row:
             self.logbutton = nb.Button(
@@ -499,7 +450,7 @@ class PreferencesDialog(tk.Toplevel):
                 variable=self.capi_fleetcarrier
             ).grid(columnspan=4, padx=self.BUTTONX, pady=self.PADY, sticky=tk.W, row=row.get())
 
-        if sys.platform in ('darwin', 'win32'):
+        if sys.platform == 'win32':
             ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid(
                 columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
             )
@@ -511,49 +462,21 @@ class PreferencesDialog(tk.Toplevel):
             with row as cur_row:
                 nb.Label(
                     config_frame,
-                    text=_('Keyboard shortcut') if  # LANG: Hotkey/Shortcut settings prompt on OSX
-                    sys.platform == 'darwin' else
-                    _('Hotkey')  # LANG: Hotkey/Shortcut settings prompt on Windows
+                    text=_('Hotkey')  # LANG: Hotkey/Shortcut settings prompt on Windows
                 ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
 
-                if sys.platform == 'darwin' and not was_accessible_at_launch:
-                    if AXIsProcessTrusted():
-                        # Shortcut settings prompt on OSX
-                        nb.Label(
-                            config_frame,
-                            # LANG: macOS Preferences > Configuration - restart the app message
-                            text=_('Re-start {APP} to use shortcuts').format(APP=applongname),
-                            foreground='firebrick'
-                        ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
+                self.hotkey_text = nb.Entry(config_frame, width=30, justify=tk.CENTER)
+                self.hotkey_text.insert(
+                    0,
+                    # No hotkey/shortcut currently defined
+                    # TODO: display Only shows up on windows
+                    # LANG: No hotkey/shortcut set
+                    hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
+                )
 
-                    else:
-                        # Shortcut settings prompt on OSX
-                        nb.Label(
-                            config_frame,
-                            # LANG: macOS - Configuration - need to grant the app permission for keyboard shortcuts
-                            text=_('{APP} needs permission to use shortcuts').format(APP=applongname),
-                            foreground='firebrick'
-                        ).grid(columnspan=4, padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
-
-                        # LANG: Shortcut settings button on OSX
-                        nb.Button(config_frame, text=_('Open System Preferences'), command=self.enableshortcuts).grid(
-                            padx=self.PADX, pady=self.BOXY, sticky=tk.E, row=cur_row
-                        )
-
-                else:
-                    self.hotkey_text = nb.Entry(config_frame, width=(
-                        20 if sys.platform == 'darwin' else 30), justify=tk.CENTER)
-                    self.hotkey_text.insert(
-                        0,
-                        # No hotkey/shortcut currently defined
-                        # TODO: display Only shows up on darwin or windows
-                        # LANG: No hotkey/shortcut set
-                        hotkeymgr.display(self.hotkey_code, self.hotkey_mods) if self.hotkey_code else _('None')
-                    )
-
-                    self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
-                    self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
-                    self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
+                self.hotkey_text.bind('<FocusIn>', self.hotkeystart)
+                self.hotkey_text.bind('<FocusOut>', self.hotkeyend)
+                self.hotkey_text.grid(column=1, columnspan=2, pady=self.BOXY, sticky=tk.W, row=cur_row)
 
                 # Hotkey/Shortcut setting
                 self.hotkey_only_btn = nb.Checkbutton(
@@ -1070,14 +993,6 @@ class PreferencesDialog(tk.Toplevel):
     def tabchanged(self, event: tk.Event) -> None:
         """Handle preferences active tab changing."""
         self.outvarchanged()
-        if sys.platform == 'darwin':
-            # Hack to recompute size so that buttons show up under Mojave
-            notebook = event.widget
-            frame = self.nametowidget(notebook.winfo_parent())
-            temp = nb.Label(frame)
-            temp.grid()
-            temp.update_idletasks()
-            temp.destroy()
 
     def outvarchanged(self, event: Optional[tk.Event] = None) -> None:
         """Handle Output tab variable changes."""
@@ -1139,16 +1054,6 @@ class PreferencesDialog(tk.Toplevel):
             entryfield.insert(0, '\\'.join(display))
 
         #                                                   None if path doesn't exist
-        elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()):
-            if pathvar.get().startswith(config.home):
-                display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[
-                    len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)):
-                ]
-
-            else:
-                display = NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())
-
-            entryfield.insert(0, '/'.join(display))
         else:
             if pathvar.get().startswith(config.home):
                 entryfield.insert(0, '~' + pathvar.get()[len(config.home):])
@@ -1288,7 +1193,7 @@ class PreferencesDialog(tk.Toplevel):
 
         config.set('capi_fleetcarrier', self.capi_fleetcarrier.get())
 
-        if sys.platform in ('darwin', 'win32'):
+        if sys.platform == 'win32':
             config.set('hotkey_code', self.hotkey_code)
             config.set('hotkey_mods', self.hotkey_mods)
             config.set('hotkey_always', int(not self.hotkey_only.get()))
@@ -1333,25 +1238,3 @@ class PreferencesDialog(tk.Toplevel):
 
         self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0)
         self.destroy()
-
-    if sys.platform == 'darwin':
-        def enableshortcuts(self) -> None:
-            """Set up macOS preferences shortcut."""
-            self.apply()
-            # popup System Preferences dialog
-            try:
-                # http://stackoverflow.com/questions/6652598/cocoa-button-opens-a-system-preference-page/6658201
-                from ScriptingBridge import SBApplication  # type: ignore
-                sysprefs = 'com.apple.systempreferences'
-                prefs = SBApplication.applicationWithBundleIdentifier_(sysprefs)
-                pane = [x for x in prefs.panes() if x.id() == 'com.apple.preference.security'][0]
-                prefs.setCurrentPane_(pane)
-                anchor = [x for x in pane.anchors() if x.name() == 'Privacy_Accessibility'][0]
-                anchor.reveal()
-                prefs.activate()
-
-            except Exception:
-                AXIsProcessTrustedWithOptions({kAXTrustedCheckOptionPrompt: True})
-
-            if not config.shutting_down:
-                self.parent.event_generate('<<Quit>>', when="tail")

From 57cd75e75ebb1a61eebac1f4b3e17b0356d6b57e Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:39:51 -0400
Subject: [PATCH 10/49] [2186] Additional Files

---
 journal_lock.py             |  4 --
 plugins/eddn.py             |  4 +-
 protocol.py                 | 63 +------------------------
 stats.py                    | 13 +----
 td.py                       |  2 +-
 tests/config/_old_config.py | 94 +------------------------------------
 theme.py                    | 21 +--------
 update.py                   | 24 ----------
 8 files changed, 9 insertions(+), 216 deletions(-)

diff --git a/journal_lock.py b/journal_lock.py
index 4d04992f..3a4dad52 100644
--- a/journal_lock.py
+++ b/journal_lock.py
@@ -218,10 +218,6 @@ class JournalLock:
             if sys.platform == 'win32':
                 self.attributes('-toolwindow', tk.TRUE)
 
-            elif sys.platform == 'darwin':
-                # http://wiki.tcl.tk/13428
-                parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
-
             self.resizable(tk.FALSE, tk.FALSE)
 
             frame = ttk.Frame(self)
diff --git a/plugins/eddn.py b/plugins/eddn.py
index 522ce6cb..b7133725 100644
--- a/plugins/eddn.py
+++ b/plugins/eddn.py
@@ -282,7 +282,7 @@ class EDDNSender:
             msg['header'] = {
                 # We have to lie and say it's *this* version, but denote that
                 # it might not actually be this version.
-                'softwareName': f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]'
+                'softwareName': f'{applongname} [{system()}]'
                                 ' (legacy replay)',
                 'softwareVersion': str(appversion_nobuild()),
                 'uploaderID': cmdr,
@@ -1074,7 +1074,7 @@ class EDDN:
             gb = this.game_build
 
         return {
-            'softwareName':    f'{applongname} [{system() if sys.platform != "darwin" else "Mac OS"}]',
+            'softwareName':    f'{applongname} [{system()}]',
             'softwareVersion': str(appversion_nobuild()),
             'uploaderID':      this.cmdr_name,
             'gameversion':     gv,
diff --git a/protocol.py b/protocol.py
index f3b956d2..0f1c157f 100644
--- a/protocol.py
+++ b/protocol.py
@@ -58,66 +58,7 @@ class GenericProtocolHandler:
             self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
 
 
-if sys.platform == 'darwin' and getattr(sys, 'frozen', False):  # noqa: C901 # its guarding ALL macos stuff.
-    import struct
-
-    import objc  # type: ignore
-    from AppKit import NSAppleEventManager, NSObject  # type: ignore
-
-    kInternetEventClass = kAEGetURL = struct.unpack('>l', b'GURL')[0]  # noqa: N816 # API names
-    keyDirectObject = struct.unpack('>l', b'----')[0]  # noqa: N816 # API names
-
-    class DarwinProtocolHandler(GenericProtocolHandler):
-        """
-        MacOS protocol handler implementation.
-
-        Uses macOS event stuff.
-        """
-
-        POLL = 100  # ms
-
-        def start(self, master: 'tkinter.Tk') -> None:
-            """Start Protocol Handler."""
-            GenericProtocolHandler.start(self, master)
-            self.lasturl: str | None = None
-            self.eventhandler = EventHandler.alloc().init()
-
-        def poll(self) -> None:
-            """Poll event until URL is updated."""
-            # No way of signalling to Tkinter from within the callback handler block that doesn't cause Python to crash,
-            # so poll. TODO: Resolved?
-            if self.lasturl and self.lasturl.startswith(self.redirect):
-                self.event(self.lasturl)
-                self.lasturl = None
-
-    class EventHandler(NSObject):
-        """Handle NSAppleEventManager IPC stuff."""
-
-        def init(self) -> None:
-            """
-            Init method for handler.
-
-            (I'd assume this is related to the subclassing of NSObject for why its not __init__)
-            """
-            self = objc.super(EventHandler, self).init()
-            NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID_(
-                self,
-                'handleEvent:withReplyEvent:',
-                kInternetEventClass,
-                kAEGetURL
-            )
-            return self
-
-        def handleEvent_withReplyEvent_(self, event, replyEvent) -> None:  # noqa: N802 N803 # Required to override
-            """Actual event handling from NSAppleEventManager."""
-            protocolhandler.lasturl = parse.unquote(
-                event.paramDescriptorForKeyword_(keyDirectObject).stringValue()
-            ).strip()
-
-            protocolhandler.master.after(DarwinProtocolHandler.POLL, protocolhandler.poll)
-
-
-elif (config.auth_force_edmc_protocol
+if (config.auth_force_edmc_protocol
       or (
           sys.platform == 'win32'
           and getattr(sys, 'frozen', False)
@@ -480,8 +421,6 @@ def get_handler_impl() -> Type[GenericProtocolHandler]:
 
     :return: An instantiatable GenericProtocolHandler
     """
-    if sys.platform == 'darwin' and getattr(sys, 'frozen', False):
-        return DarwinProtocolHandler  # pyright: reportUnboundVariable=false
 
     if (
         (sys.platform == 'win32' and config.auth_force_edmc_protocol)
diff --git a/stats.py b/stats.py
index 4351e297..c377e5d3 100644
--- a/stats.py
+++ b/stats.py
@@ -374,7 +374,7 @@ class StatsResults(tk.Toplevel):
             self.transient(parent)
 
         # position over parent
-        if sys.platform != 'darwin' or parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+        if parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
             self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}")
 
         # remove decoration
@@ -382,10 +382,6 @@ class StatsResults(tk.Toplevel):
         if sys.platform == 'win32':
             self.attributes('-toolwindow', tk.TRUE)
 
-        elif sys.platform == 'darwin':
-            # http://wiki.tcl.tk/13428
-            parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility')
-
         frame = ttk.Frame(self)
         frame.grid(sticky=tk.NSEW)
 
@@ -423,13 +419,6 @@ class StatsResults(tk.Toplevel):
         ttk.Frame(page).grid(pady=5)         # bottom spacer
         notebook.add(page, text=_('Ships'))  # LANG: Status dialog title
 
-        if sys.platform != 'darwin':
-            buttonframe = ttk.Frame(frame)
-            buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW)  # type: ignore # the tuple is supported
-            buttonframe.columnconfigure(0, weight=1)
-            ttk.Label(buttonframe).grid(row=0, column=0)  # spacer
-            ttk.Button(buttonframe, text='OK', command=self.destroy).grid(row=0, column=1, sticky=tk.E)
-
         # wait for window to appear on screen before calling grab_set
         self.wait_visibility()
         self.grab_set()
diff --git a/td.py b/td.py
index 6a588f57..d2b5dbdd 100644
--- a/td.py
+++ b/td.py
@@ -32,7 +32,7 @@ def export(data: CAPIData) -> None:
     with open(data_path / data_filename, 'wb') as h:
         # Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
         h.write('#! trade.py import -\n'.encode('utf-8'))
-        this_platform = "Mac OS" if sys.platform == 'darwin' else system()
+        this_platform = system()
         cmdr_name = data['commander']['name'].strip()
         h.write(
             f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')
diff --git a/tests/config/_old_config.py b/tests/config/_old_config.py
index 690c75eb..35ae19e3 100644
--- a/tests/config/_old_config.py
+++ b/tests/config/_old_config.py
@@ -13,13 +13,7 @@ from EDMCLogging import get_main_logger
 
 logger = get_main_logger()
 
-if sys.platform == 'darwin':
-    from Foundation import (  # type: ignore
-        NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains,
-        NSUserDefaults, NSUserDomainMask
-    )
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     import ctypes
     import uuid
     from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR
@@ -115,91 +109,7 @@ class OldConfig:
     OUT_EDDN_DELAY = 4096
     OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
 
-    if sys.platform == 'darwin':  # noqa: C901 # It's gating *all* the functions
-
-        def __init__(self):
-            self.app_dir = join(
-                NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0], appname
-            )
-            if not isdir(self.app_dir):
-                mkdir(self.app_dir)
-
-            self.plugin_dir = join(self.app_dir, 'plugins')
-            if not isdir(self.plugin_dir):
-                mkdir(self.plugin_dir)
-
-            if getattr(sys, 'frozen', False):
-                self.internal_plugin_dir = normpath(join(dirname(sys.executable), pardir, 'Library', 'plugins'))
-                self.respath = normpath(join(dirname(sys.executable), pardir, 'Resources'))
-                self.identifier = NSBundle.mainBundle().bundleIdentifier()
-
-            else:
-                self.internal_plugin_dir = join(dirname(__file__), 'plugins')
-                self.respath = dirname(__file__)
-                # Don't use Python's settings if interactive
-                self.identifier = f'uk.org.marginal.{appname.lower()}'
-                NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier
-
-            self.default_journal_dir: str | None = join(
-                NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0],
-                'Frontier Developments',
-                'Elite Dangerous'
-            )
-            self.home = expanduser('~')
-
-            self.defaults = NSUserDefaults.standardUserDefaults()
-            self.settings = dict(self.defaults.persistentDomainForName_(self.identifier) or {})  # make writeable
-
-            # Check out_dir exists
-            if not self.get('outdir') or not isdir(str(self.get('outdir'))):
-                self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
-
-        def get(self, key: str, default: None | list | str = None) -> None | list | str:
-            """Look up a string configuration value."""
-            val = self.settings.get(key)
-            if val is None:
-                return default
-
-            if isinstance(val, str):
-                return str(val)
-
-            if isinstance(val, list):
-                return list(val)  # make writeable
-
-            return default
-
-        def getint(self, key: str, default: int = 0) -> int:
-            """Look up an integer configuration value."""
-            try:
-                return int(self.settings.get(key, default))  # should already be int, but check by casting
-
-            except ValueError as e:
-                logger.error(f"Failed to int({key=})", exc_info=e)
-                return default
-
-            except Exception as e:
-                logger.debug('The exception type is ...', exc_info=e)
-                return default
-
-        def set(self, key: str, val: int | str | list) -> None:
-            """Set value on the specified configuration key."""
-            self.settings[key] = val
-
-        def delete(self, key: str) -> None:
-            """Delete the specified configuration key."""
-            self.settings.pop(key, None)
-
-        def save(self) -> None:
-            """Save current configuration to disk."""
-            self.defaults.setPersistentDomain_forName_(self.settings, self.identifier)
-            self.defaults.synchronize()
-
-        def close(self) -> None:
-            """Close the configuration."""
-            self.save()
-            self.defaults = None
-
-    elif sys.platform == 'win32':
+    if sys.platform == 'win32':
 
         def __init__(self):
             self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname)  # type: ignore
diff --git a/theme.py b/theme.py
index bfb76c55..b128bc91 100644
--- a/theme.py
+++ b/theme.py
@@ -268,8 +268,7 @@ class _Theme:
             # (Mostly) system colors
             style = ttk.Style()
             self.current = {
-                'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or
-                               style.lookup('TLabel', 'background')),
+                'background': (style.lookup('TLabel', 'background')),
                 'foreground': style.lookup('TLabel', 'foreground'),
                 'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or
                                      style.lookup('TLabel', 'background', ['active'])),
@@ -366,8 +365,6 @@ class _Theme:
                 if 'bg' not in attribs:
                     widget['background'] = self.current['background']
                     widget['activebackground'] = self.current['activebackground']
-                    if sys.platform == 'darwin' and isinstance(widget, tk.Button):
-                        widget['highlightbackground'] = self.current['background']
 
                 if 'font' not in attribs:
                     widget['font'] = self.current['font']
@@ -426,21 +423,7 @@ class _Theme:
             return  # Don't need to mess with the window manager
         self.active = theme
 
-        if sys.platform == 'darwin':
-            from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask
-            root.update_idletasks()  # need main window to be created
-            if theme == self.THEME_DEFAULT:
-                appearance = NSAppearance.appearanceNamed_('NSAppearanceNameAqua')
-
-            else:  # Dark (Transparent only on win32)
-                appearance = NSAppearance.appearanceNamed_('NSAppearanceNameDarkAqua')
-
-            for window in NSApplication.sharedApplication().windows():
-                window.setStyleMask_(window.styleMask() & ~(
-                    NSMiniaturizableWindowMask | NSResizableWindowMask))  # disable zoom
-                window.setAppearance_(appearance)
-
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             GWL_STYLE = -16  # noqa: N806 # ctypes
             WS_MAXIMIZEBOX = 0x00010000  # noqa: N806 # ctypes
             # tk8.5.9/win/tkWinWm.c:342
diff --git a/update.py b/update.py
index 024aeb09..e0b8f97b 100644
--- a/update.py
+++ b/update.py
@@ -115,21 +115,6 @@ class Updater:
 
             return
 
-        if sys.platform == 'darwin':
-            import objc
-
-            try:
-                objc.loadBundle(
-                    'Sparkle', globals(), join(dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework')
-                )
-                # loadBundle presumably supplies `SUUpdater`
-                self.updater = SUUpdater.sharedUpdater()  # noqa: F821
-
-            except Exception:
-                # can't load framework - not frozen or not included in app bundle?
-                print_exc()
-                self.updater = None
-
     def set_automatic_updates_check(self, onoroff: bool) -> None:
         """
         Set (Win)Sparkle to perform automatic update checks, or not.
@@ -142,9 +127,6 @@ class Updater:
         if sys.platform == 'win32' and self.updater:
             self.updater.win_sparkle_set_automatic_check_for_updates(onoroff)
 
-        if sys.platform == 'darwin' and self.updater:
-            self.updater.SUEnableAutomaticChecks(onoroff)
-
     def check_for_updates(self) -> None:
         """Trigger the requisite method to check for an update."""
         if self.use_internal():
@@ -155,9 +137,6 @@ class Updater:
         elif sys.platform == 'win32' and self.updater:
             self.updater.win_sparkle_check_update_with_ui()
 
-        elif sys.platform == 'darwin' and self.updater:
-            self.updater.checkForUpdates_(None)
-
     def check_appcast(self) -> EDMCVersion | None:
         """
         Manually (no Sparkle or WinSparkle) check the update_feed appcast file.
@@ -184,9 +163,6 @@ class Updater:
 
             return None
 
-        if sys.platform == 'darwin':
-            sparkle_platform = 'macos'
-
         else:
             # For *these* purposes anything else is the same as 'windows', as
             # non-win32 would be running from source.

From 05eaf059389a25d16c778ea6861e0836a71184f3 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 19:41:01 -0400
Subject: [PATCH 11/49] [Minor] Remove Unused Import

For Now
---
 ttkHyperlinkLabel.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index 0ca87fc7..3b65d6b9 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -22,7 +22,6 @@ from __future__ import annotations
 
 import sys
 import tkinter as tk
-import warnings
 import webbrowser
 from tkinter import font as tk_font
 from tkinter import ttk

From 08818785d051b81a7ad88c6975b0be0df88a07c7 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 20:03:23 -0400
Subject: [PATCH 12/49] [2186] HyperLinkLabel first pass

---
 ttkHyperlinkLabel.py | 21 ++++++---------------
 1 file changed, 6 insertions(+), 15 deletions(-)

diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index 3b65d6b9..de906510 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -32,7 +32,7 @@ if TYPE_CHECKING:
 
 
 # FIXME: Split this into multi-file module to separate the platforms
-class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # type: ignore
+class HyperlinkLabel(tk.Label or ttk.Label):  # type: ignore
     """Clickable label for HTTP links."""
 
     def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None:
@@ -50,22 +50,14 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
         self.foreground = kw.get('foreground', 'blue')
         self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup(
             'TLabel', 'foreground', ('disabled',)))  # ttk.Label doesn't support disabledforeground option
-
-        if sys.platform == 'darwin':
-            # Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult
-            kw['background'] = kw.pop('background', 'systemDialogBackgroundActive')
-            kw['anchor'] = kw.pop('anchor', tk.W)  # like ttk.Label
-            tk.Label.__init__(self, master, **kw)
-
-        else:
-            ttk.Label.__init__(self, master, **kw)
+        ttk.Label.__init__(self, master, **kw)
 
         self.bind('<Button-1>', self._click)
 
         self.menu = tk.Menu(tearoff=tk.FALSE)
         # LANG: Label for 'Copy' as in 'Copy and Paste'
         self.menu.add_command(label=_('Copy'), command=self.copy)  # As in Copy and Paste
-        self.bind(sys.platform == 'darwin' and '<Button-2>' or '<Button-3>', self._contextmenu)
+        self.bind('<Button-3>', self._contextmenu)
 
         self.bind('<Enter>', self._enter)
         self.bind('<Leave>', self._leave)
@@ -106,10 +98,9 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
             if state == tk.DISABLED:
                 kw['cursor'] = 'arrow'  # System default
             elif self.url and (kw['text'] if 'text' in kw else self['text']):
-                kw['cursor'] = 'pointinghand' if sys.platform == 'darwin' else 'hand2'
+                kw['cursor'] = 'hand2'
             else:
-                kw['cursor'] = 'notallowed' if sys.platform == 'darwin' else (
-                    'no' if sys.platform == 'win32' else 'circle')
+                kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle')
 
         return super().configure(cnf, **kw)
 
@@ -139,7 +130,7 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label):  # typ
 
     def _contextmenu(self, event: tk.Event) -> None:
         if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
-            self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root)
+            self.menu.post(event.x_root, event.y_root)
 
     def copy(self) -> None:
         """Copy the current text to the clipboard."""

From f7b39f8dafedf453f6f80e278f88d8e12ccced60 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 20:10:55 -0400
Subject: [PATCH 13/49] [2186] MyNB First Pass

---
 myNotebook.py | 66 +++++++--------------------------------------------
 1 file changed, 9 insertions(+), 57 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index dbfc9250..efae591c 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -16,13 +16,7 @@ import sys
 import tkinter as tk
 from tkinter import ttk
 
-# Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult
-if sys.platform == 'darwin':
-    from platform import mac_ver
-    PAGEFG = 'systemButtonText'
-    PAGEBG = 'systemButtonActiveDarkShadow'
-
-elif sys.platform == 'win32':
+if sys.platform == 'win32':
     PAGEFG = 'SystemWindowText'
     PAGEBG = 'SystemWindow'  # typically white
 
@@ -35,14 +29,7 @@ class Notebook(ttk.Notebook):
         ttk.Notebook.__init__(self, master, **kw)
         style = ttk.Style()
 
-        if sys.platform == 'darwin':
-            if list(map(int, mac_ver()[0].split('.'))) >= [10, 10]:
-                # Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see
-                # https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25
-                style.configure('TNotebook.Tab', padding=(12, 10, 12, 2))
-                style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')])
-            self.grid(sticky=tk.NSEW)  # Already padded apropriately
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             style.configure('nb.TFrame',                          background=PAGEBG)
             style.configure('nb.TButton',                         background=PAGEBG)
             style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
@@ -60,11 +47,7 @@ class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame):  # type: ignore
     """Custom t(t)k.Frame class to fix some display issues."""
 
     def __init__(self, master: ttk.Notebook | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['background'] = kw.pop('background', PAGEBG)
-            tk.Frame.__init__(self, master, **kw)
-            tk.Frame(self).grid(pady=5)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Frame.__init__(self, master, style='nb.TFrame', **kw)
             ttk.Frame(self).grid(pady=5)  # top spacer
         else:
@@ -91,21 +74,14 @@ class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry):  # type: ignore
     """Custom t(t)k.Entry class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
-            tk.Entry.__init__(self, master, **kw)
-        else:
-            ttk.Entry.__init__(self, master, **kw)
+        ttk.Entry.__init__(self, master, **kw)
 
 
 class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):  # type: ignore
     """Custom t(t)k.Button class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
-            tk.Button.__init__(self, master, **kw)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Button.__init__(self, master, style='nb.TButton', **kw)
         else:
             ttk.Button.__init__(self, master, **kw)
@@ -115,29 +91,14 @@ class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button):  # type
     """Custom t(t)k.ColoredButton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            # Can't set Button background on OSX, so use a Label instead
-            kw['relief'] = kw.pop('relief', tk.RAISED)
-            self._command = kw.pop('command', None)
-            tk.Label.__init__(self, master, **kw)
-            self.bind('<Button-1>', self._press)
-        else:
-            tk.Button.__init__(self, master, **kw)
-
-    if sys.platform == 'darwin':
-        def _press(self, event):
-            self._command()
+        tk.Button.__init__(self, master, **kw)
 
 
 class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton):  # type: ignore
     """Custom t(t)k.Checkbutton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['foreground'] = kw.pop('foreground', PAGEFG)
-            kw['background'] = kw.pop('background', PAGEBG)
-            tk.Checkbutton.__init__(self, master, **kw)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw)
         else:
             ttk.Checkbutton.__init__(self, master, **kw)
@@ -147,11 +108,7 @@ class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton
     """Custom t(t)k.Radiobutton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'darwin':
-            kw['foreground'] = kw.pop('foreground', PAGEFG)
-            kw['background'] = kw.pop('background', PAGEBG)
-            tk.Radiobutton.__init__(self, master, **kw)
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw)
         else:
             ttk.Radiobutton.__init__(self, master, **kw)
@@ -161,12 +118,7 @@ class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu):
     """Custom t(t)k.OptionMenu class to fix some display issues."""
 
     def __init__(self, master, variable, default=None, *values, **kw):
-        if sys.platform == 'darwin':
-            variable.set(default)
-            bg = kw.pop('background', PAGEBG)
-            tk.OptionMenu.__init__(self, master, variable, *values, **kw)
-            self['background'] = bg
-        elif sys.platform == 'win32':
+        if sys.platform == 'win32':
             # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
             ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
             self['menu'].configure(background=PAGEBG)

From c1b8533cb41c2e33d63311dac120ff7b0ddd4c29 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 21:22:35 -0400
Subject: [PATCH 14/49] [2186] Simplify myNB Files

---
 docs/examples/click_counter/load.py |  4 +-
 myNotebook.py                       | 95 ++++++++++++-----------------
 plugins/coriolis.py                 |  8 +--
 plugins/edsm.py                     |  8 +--
 plugins/inara.py                    |  4 +-
 prefs.py                            | 22 +++----
 6 files changed, 62 insertions(+), 79 deletions(-)

diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py
index ad90a084..f7f23837 100644
--- a/docs/examples/click_counter/load.py
+++ b/docs/examples/click_counter/load.py
@@ -7,6 +7,8 @@ from __future__ import annotations
 
 import logging
 import tkinter as tk
+from tkinter import ttk
+
 import myNotebook as nb  # noqa: N813
 from config import appname, config
 
@@ -63,7 +65,7 @@ class ClickCounter:
 
         # setup our config in a "Click Count: number"
         nb.Label(frame, text='Click Count').grid(row=current_row)
-        nb.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
+        ttk.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
         current_row += 1  # Always increment our row counter, makes for far easier tkinter design.
         return frame
 
diff --git a/myNotebook.py b/myNotebook.py
index efae591c..528664ba 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -1,12 +1,9 @@
 """
 Custom `ttk.Notebook` to fix various display issues.
 
-Hacks to fix various display issues with notebooks and their child widgets on
-OSX and Windows.
+Hacks to fix various display issues with notebooks and their child widgets on Windows.
 
 - Windows: page background should be White, not SystemButtonFace
-- OSX:     page background should be a darker gray than systemWindowBody
-           selected tab foreground should be White when the window is active
 
 Entire file may be imported by plugins.
 """
@@ -26,24 +23,17 @@ class Notebook(ttk.Notebook):
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
 
-        ttk.Notebook.__init__(self, master, **kw)
+        super().__init__(master, **kw)
         style = ttk.Style()
-
-        if sys.platform == 'win32':
-            style.configure('nb.TFrame',                          background=PAGEBG)
-            style.configure('nb.TButton',                         background=PAGEBG)
-            style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
-            style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
-            style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
-            self.grid(padx=10, pady=10, sticky=tk.NSEW)
-        else:
-            self.grid(padx=10, pady=10, sticky=tk.NSEW)
+        style.configure('nb.TFrame',                          background=PAGEBG)
+        style.configure('nb.TButton',                         background=PAGEBG)
+        style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
+        style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
+        style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
+        self.grid(padx=10, pady=10, sticky=tk.NSEW)
 
 
-# FIXME: The real fix for this 'dynamic type' would be to split this whole
-#  thing into being a module with per-platform files, as we've done with config
-#  That would also make the code cleaner.
-class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame):  # type: ignore
+class Frame(tk.Frame or ttk.Frame):  # type: ignore
     """Custom t(t)k.Frame class to fix some display issues."""
 
     def __init__(self, master: ttk.Notebook | None = None, **kw):
@@ -60,26 +50,25 @@ class Label(tk.Label):
     """Custom tk.Label class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
-        if sys.platform == 'win32':
-            kw['foreground'] = kw.pop('foreground', PAGEFG)
-            kw['background'] = kw.pop('background', PAGEBG)
-        else:
-            kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground'))
-            kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background'))
-        tk.Label.__init__(self, master, **kw)  # Just use tk.Label on all platforms
+        kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32'
+                                  else ttk.Style().lookup('TLabel', 'foreground'))
+        kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32'
+                                  else ttk.Style().lookup('TLabel', 'background'))
+        super().__init__(master, **kw)
 
 
-class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry):  # type: ignore
+class Entry(ttk.Entry):  # type: ignore
     """Custom t(t)k.Entry class to fix some display issues."""
 
+    # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        ttk.Entry.__init__(self, master, **kw)
+        super().__init__(master, **kw)
 
 
-class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):  # type: ignore
+class Button(tk.Button or ttk.Button):  # type: ignore
     """Custom t(t)k.Button class to fix some display issues."""
 
+    # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
         if sys.platform == 'win32':
             ttk.Button.__init__(self, master, style='nb.TButton', **kw)
@@ -87,47 +76,39 @@ class Button(sys.platform == 'darwin' and tk.Button or ttk.Button):  # type: ign
             ttk.Button.__init__(self, master, **kw)
 
 
-class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button):  # type: ignore
+class ColoredButton(tk.Label or tk.Button):  # type: ignore
     """Custom t(t)k.ColoredButton class to fix some display issues."""
 
+    # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
         tk.Button.__init__(self, master, **kw)
 
 
-class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton):  # type: ignore
+class Checkbutton(ttk.Checkbutton):
     """Custom t(t)k.Checkbutton class to fix some display issues."""
 
-    def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'win32':
-            ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw)
-        else:
-            ttk.Checkbutton.__init__(self, master, **kw)
+    def __init__(self, master=None, **kw):
+        style = 'nb.TCheckbutton' if sys.platform == 'win32' else None
+        super().__init__(master, style=style, **kw)  # type: ignore
 
 
-class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton):  # type: ignore
+class Radiobutton(ttk.Radiobutton):
     """Custom t(t)k.Radiobutton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        if sys.platform == 'win32':
-            ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw)
-        else:
-            ttk.Radiobutton.__init__(self, master, **kw)
+        style = 'nb.TRadiobutton' if sys.platform == 'win32' else None
+        super().__init__(master, style=style, **kw)  # type: ignore
 
 
-class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu):  # type: ignore
-    """Custom t(t)k.OptionMenu class to fix some display issues."""
+class OptionMenu(ttk.OptionMenu):
+    """Custom ttk.OptionMenu class to fix some display issues."""
 
     def __init__(self, master, variable, default=None, *values, **kw):
-        if sys.platform == 'win32':
-            # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
-            ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
-            self['menu'].configure(background=PAGEBG)
-            # Workaround for https://bugs.python.org/issue25684
-            for i in range(0, self['menu'].index('end')+1):
-                self['menu'].entryconfig(i, variable=variable)
-        else:
-            ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
-            self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
-            # Workaround for https://bugs.python.org/issue25684
-            for i in range(0, self['menu'].index('end')+1):
-                self['menu'].entryconfig(i, variable=variable)
+        style = 'nb.TMenubutton' if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
+        menu_background = PAGEBG if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
+
+        super().__init__(master, variable, default, *values, style=style, **kw)
+        self['menu'].configure(background=menu_background)
+
+        for i in range(self['menu'].index('end') + 1):
+            self['menu'].entryconfig(i, variable=variable)
diff --git a/plugins/coriolis.py b/plugins/coriolis.py
index c142c686..20da16b1 100644
--- a/plugins/coriolis.py
+++ b/plugins/coriolis.py
@@ -106,12 +106,12 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
 
     # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
-    nb.Entry(conf_frame,
+    ttk.Entry(conf_frame,
              textvariable=coriolis_config.normal_textvar).grid(
                 sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
             )
     # LANG: Generic 'Reset' button label
-    nb.Button(conf_frame, text=_("Reset"),
+    ttk.Button(conf_frame, text=_("Reset"),
               command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
@@ -119,11 +119,11 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
 
     # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
-    nb.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
+    ttk.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
         sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
     )
     # LANG: Generic 'Reset' button label
-    nb.Button(conf_frame, text=_('Reset'),
+    ttk.Button(conf_frame, text=_('Reset'),
               command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
diff --git a/plugins/edsm.py b/plugins/edsm.py
index 46d05f4c..146dd5ab 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -113,10 +113,10 @@ class This:
         self.cmdr_text: nb.Label | None = None
 
         self.user_label: nb.Label | None = None
-        self.user: nb.Entry | None = None
+        self.user: ttk.Entry | None = None
 
         self.apikey_label: nb.Label | None = None
-        self.apikey: nb.Entry | None = None
+        self.apikey: ttk.Entry | None = None
 
 
 this = This()
@@ -345,14 +345,14 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
     # LANG: EDSM Commander name label in EDSM settings
     this.user_label = nb.Label(frame, text=_('Commander Name'))
     this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.user = nb.Entry(frame)
+    this.user = ttk.Entry(frame)
     this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
 
     cur_row += 1
     # LANG: EDSM API key label
     this.apikey_label = nb.Label(frame, text=_('API Key'))
     this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.apikey = nb.Entry(frame, show="*", width=50)
+    this.apikey = ttk.Entry(frame, show="*", width=50)
     this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
     cur_row += 1
 
diff --git a/plugins/inara.py b/plugins/inara.py
index 0e0eb7bf..d96e05ef 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -125,7 +125,7 @@ class This:
         self.log: 'tk.IntVar'
         self.log_button: nb.Checkbutton
         self.label: HyperlinkLabel
-        self.apikey: nb.Entry
+        self.apikey: ttk.Entry
         self.apikey_label: tk.Label
 
         self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
@@ -292,7 +292,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame:
     # LANG: Inara API key label
     this.apikey_label = nb.Label(frame, text=_('API Key'))  # Inara setting
     this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.apikey = nb.Entry(frame, show="*", width=50)
+    this.apikey = ttk.Entry(frame, show="*", width=50)
     this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
     cur_row += 1
 
diff --git a/prefs.py b/prefs.py
index 862bda27..ef43c5de 100644
--- a/prefs.py
+++ b/prefs.py
@@ -361,12 +361,12 @@ class PreferencesDialog(tk.Toplevel):
         # Type ignored due to incorrect type annotation. a 2 tuple does padding for each side
         self.outdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())  # type: ignore
 
-        self.outdir_entry = nb.Entry(output_frame, takefocus=False)
+        self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
         self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
         text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
 
-        self.outbutton = nb.Button(
+        self.outbutton = ttk.Button(
             output_frame,
             text=text,
             # Technically this is different from the label in Settings > Output, as *this* is used
@@ -399,7 +399,7 @@ class PreferencesDialog(tk.Toplevel):
             logdir = default
 
         self.logdir.set(logdir)
-        self.logdir_entry = nb.Entry(config_frame, takefocus=False)
+        self.logdir_entry = ttk.Entry(config_frame, takefocus=False)
 
         # Location of the Journal files
         nb.Label(
@@ -413,7 +413,7 @@ class PreferencesDialog(tk.Toplevel):
         text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
 
         with row as cur_row:
-            self.logbutton = nb.Button(
+            self.logbutton = ttk.Button(
                 config_frame,
                 text=text,
                 # LANG: Settings > Configuration - Label for Journal files location
@@ -423,7 +423,7 @@ class PreferencesDialog(tk.Toplevel):
 
             if config.default_journal_dir_path:
                 # Appearance theme and language setting
-                nb.Button(
+                ttk.Button(
                     config_frame,
                     # LANG: Settings > Configuration - Label on 'reset journal files location to default' button
                     text=_('Default'),
@@ -465,7 +465,7 @@ class PreferencesDialog(tk.Toplevel):
                     text=_('Hotkey')  # LANG: Hotkey/Shortcut settings prompt on Windows
                 ).grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
 
-                self.hotkey_text = nb.Entry(config_frame, width=30, justify=tk.CENTER)
+                self.hotkey_text = ttk.Entry(config_frame, width=30, justify=tk.CENTER)
                 self.hotkey_text.insert(
                     0,
                     # No hotkey/shortcut currently defined
@@ -623,7 +623,7 @@ class PreferencesDialog(tk.Toplevel):
             self.loglevel_dropdown.configure(width=15)
             self.loglevel_dropdown.grid(column=1, pady=self.BOXY, sticky=tk.W, row=cur_row)
 
-            nb.Button(
+            ttk.Button(
                 config_frame,
                 # LANG: Label on button used to open a filesystem folder
                 text=_('Open Log Folder'),  # Button that opens a folder in Explorer/Finder
@@ -726,7 +726,7 @@ class PreferencesDialog(tk.Toplevel):
             self.theme_label_0.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
 
             # Main window
-            self.theme_button_0 = nb.ColoredButton(
+            self.theme_button_0 = tk.Button(
                 appearance_frame,
                 # LANG: Appearance - Example 'Normal' text
                 text=_('Station'),
@@ -739,7 +739,7 @@ class PreferencesDialog(tk.Toplevel):
         with row as cur_row:
             self.theme_label_1 = nb.Label(appearance_frame, text=self.theme_prompts[1])
             self.theme_label_1.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=cur_row)
-            self.theme_button_1 = nb.ColoredButton(
+            self.theme_button_1 = tk.Button(
                 appearance_frame,
                 text='  Hutton Orbital  ',  # Do not translate
                 background='grey4',
@@ -870,7 +870,7 @@ class PreferencesDialog(tk.Toplevel):
             padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
         )
 
-        plugdirentry = nb.Entry(plugins_frame, justify=tk.LEFT)
+        plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT)
         self.displaypath(plugdir, plugdirentry)
         plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
@@ -882,7 +882,7 @@ class PreferencesDialog(tk.Toplevel):
                 text=_("Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
             ).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
 
-            nb.Button(
+            ttk.Button(
                 plugins_frame,
                 # LANG: Label on button used to open a filesystem folder
                 text=_('Open'),  # Button that opens a folder in Explorer/Finder

From ae74d949a892b919a275c2cda45e951b97c3082c Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 21:24:11 -0400
Subject: [PATCH 15/49] [Nit] Indentation, Yay!

---
 plugins/coriolis.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/plugins/coriolis.py b/plugins/coriolis.py
index 20da16b1..a97eb6c6 100644
--- a/plugins/coriolis.py
+++ b/plugins/coriolis.py
@@ -107,12 +107,12 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
     # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
     ttk.Entry(conf_frame,
-             textvariable=coriolis_config.normal_textvar).grid(
+              textvariable=coriolis_config.normal_textvar).grid(
                 sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
             )
     # LANG: Generic 'Reset' button label
     ttk.Button(conf_frame, text=_("Reset"),
-              command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
+               command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
     cur_row += 1
@@ -124,7 +124,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Fr
     )
     # LANG: Generic 'Reset' button label
     ttk.Button(conf_frame, text=_('Reset'),
-              command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
+               command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
     cur_row += 1

From cfb6f729ab5888238c552aa0bb0d568b69356fbb Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 21:27:22 -0400
Subject: [PATCH 16/49] [2186] Remove Unused Translations

---
 L10n/en.template   | 24 ------------------------
 config/__init__.py |  2 +-
 2 files changed, 1 insertion(+), 25 deletions(-)

diff --git a/L10n/en.template b/L10n/en.template
index f5acb377..7ba67a61 100644
--- a/L10n/en.template
+++ b/L10n/en.template
@@ -78,12 +78,6 @@
 /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
 "Edit" = "Edit";
 
-/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
-"View" = "View";
-
-/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
-"Window" = "Window";
-
 /* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
 "Help" = "Help";
 
@@ -351,9 +345,6 @@
 /* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
 "Error: Inara {MSG}" = "Error: Inara {MSG}";
 
-/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
-"Preferences" = "Preferences";
-
 /* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
 "Please choose what data to save" = "Please choose what data to save";
 
@@ -372,9 +363,6 @@
 /* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
 "File location" = "File location";
 
-/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
-"Change..." = "Change...";
-
 /* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
 "Browse..." = "Browse...";
 
@@ -390,21 +378,9 @@
 /* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
 "Enable Fleetcarrier CAPI Queries" = "Enable Fleetcarrier CAPI Queries";
 
-/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
-"Keyboard shortcut" = "Keyboard shortcut";
-
 /* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
 "Hotkey" = "Hotkey";
 
-/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
-"Re-start {APP} to use shortcuts" = "Re-start {APP} to use shortcuts";
-
-/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
-"{APP} needs permission to use shortcuts" = "{APP} needs permission to use shortcuts";
-
-/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
-"Open System Preferences" = "Open System Preferences";
-
 /* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
 "Only when Elite: Dangerous is the active app" = "Only when Elite: Dangerous is the active app";
 
diff --git a/config/__init__.py b/config/__init__.py
index 992710a0..c6c404b9 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -54,7 +54,7 @@ appcmdname = 'EDMC'
 # <https://semver.org/#semantic-versioning-specification-semver>
 # Major.Minor.Patch(-prerelease)(+buildmetadata)
 # NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
-_static_appversion = '5.10.3'
+_static_appversion = '5.11.0-alpha0'
 
 _cached_version: semantic_version.Version | None = None
 copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'

From 016fb96e065b45bd803587b31edb9619809f7198 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 22:01:49 -0400
Subject: [PATCH 17/49] [2186] General Cleanup

---
 prefs.py             | 4 ++--
 td.py                | 1 -
 ttkHyperlinkLabel.py | 1 -
 update.py            | 9 +++------
 4 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/prefs.py b/prefs.py
index ef43c5de..307e3c7a 100644
--- a/prefs.py
+++ b/prefs.py
@@ -364,7 +364,7 @@ class PreferencesDialog(tk.Toplevel):
         self.outdir_entry = ttk.Entry(output_frame, takefocus=False)
         self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        text = (_('Browse...'))  # LANG: NOT-macOS Settings - files location selection button
+        text = _('Browse...')  # LANG: NOT-macOS Settings - files location selection button
 
         self.outbutton = ttk.Button(
             output_frame,
@@ -410,7 +410,7 @@ class PreferencesDialog(tk.Toplevel):
 
         self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
 
-        text = (_('Browse...'))  # LANG: NOT-macOS Setting - files location selection button
+        text = _('Browse...')  # LANG: NOT-macOS Setting - files location selection button
 
         with row as cur_row:
             self.logbutton = ttk.Button(
diff --git a/td.py b/td.py
index d2b5dbdd..484e8d29 100644
--- a/td.py
+++ b/td.py
@@ -1,7 +1,6 @@
 """Export data for Trade Dangerous."""
 
 import pathlib
-import sys
 import time
 from collections import defaultdict
 from operator import itemgetter
diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py
index de906510..9bb3b9bf 100644
--- a/ttkHyperlinkLabel.py
+++ b/ttkHyperlinkLabel.py
@@ -31,7 +31,6 @@ if TYPE_CHECKING:
     def _(x: str) -> str: return x
 
 
-# FIXME: Split this into multi-file module to separate the platforms
 class HyperlinkLabel(tk.Label or ttk.Label):  # type: ignore
     """Clickable label for HTTP links."""
 
diff --git a/update.py b/update.py
index e0b8f97b..346aff63 100644
--- a/update.py
+++ b/update.py
@@ -7,10 +7,8 @@ See LICENSE file.
 """
 from __future__ import annotations
 
-import os
 import sys
 import threading
-from os.path import dirname, join
 from traceback import print_exc
 from typing import TYPE_CHECKING
 from xml.etree import ElementTree
@@ -163,10 +161,9 @@ class Updater:
 
             return None
 
-        else:
-            # For *these* purposes anything else is the same as 'windows', as
-            # non-win32 would be running from source.
-            sparkle_platform = 'windows'
+        # For *these* purposes anything else is the same as 'windows', as
+        # non-win32 would be running from source.
+        sparkle_platform = 'windows'
 
         for item in feed.findall('channel/item'):
             # xml is a pain with types, hence these ignores

From 1800f8f0b18cb8e13a7cc032d4eac61f7f67cbb1 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 22:17:21 -0400
Subject: [PATCH 18/49] [2186] Remove Some Comments

---
 config/__init__.py | 1 -
 dashboard.py       | 4 ++--
 monitor.py         | 2 +-
 prefs.py           | 2 +-
 theme.py           | 2 +-
 5 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/config/__init__.py b/config/__init__.py
index c6c404b9..99e8fdc6 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -7,7 +7,6 @@ See LICENSE file.
 
 Windows uses the Registry to store values in a flat manner.
 Linux uses a file, but for commonality it's still a flat data structure.
-macOS uses a 'defaults' object.
 """
 from __future__ import annotations
 
diff --git a/dashboard.py b/dashboard.py
index 3948c92b..4776319c 100644
--- a/dashboard.py
+++ b/dashboard.py
@@ -26,7 +26,7 @@ if sys.platform == 'win32':
 else:
     # Linux's inotify doesn't work over CIFS or NFS, so poll
     class FileSystemEventHandler:  # type: ignore
-        """Dummy class to represent a file system event handler on platforms other than macOS and Windows."""
+        """Dummy class to represent a file system event handler on platforms other than Windows."""
 
 
 class Dashboard(FileSystemEventHandler):
@@ -160,7 +160,7 @@ class Dashboard(FileSystemEventHandler):
 
     def on_modified(self, event) -> None:
         """
-        Watchdog callback - DirModifiedEvent on macOS, FileModifiedEvent on Windows.
+        Watchdog callback - FileModifiedEvent on Windows.
 
         :param event: Watchdog event.
         """
diff --git a/monitor.py b/monitor.py
index d549e533..5acbcbc4 100644
--- a/monitor.py
+++ b/monitor.py
@@ -439,7 +439,7 @@ class EDLogs(FileSystemEventHandler):
                     new_journal_file = None
 
             if logfile:
-                loghandle.seek(0, SEEK_END)		  # required to make macOS notice log change over SMB
+                loghandle.seek(0, SEEK_END)  # required to make macOS notice log change over SMB. # TODO: Do we need this?
                 loghandle.seek(log_pos, SEEK_SET)  # reset EOF flag # TODO: log_pos reported as possibly unbound
                 for line in loghandle:
                     # Paranoia check to see if we're shutting down
diff --git a/prefs.py b/prefs.py
index 307e3c7a..5b045b1a 100644
--- a/prefs.py
+++ b/prefs.py
@@ -285,7 +285,7 @@ class PreferencesDialog(tk.Toplevel):
 
         # wait for window to appear on screen before calling grab_set
         self.parent.update_idletasks()
-        self.parent.wm_attributes('-topmost', 0)  # needed for dialog to appear ontop of parent on OSX & Linux
+        self.parent.wm_attributes('-topmost', 0)  # needed for dialog to appear ontop of parent on Linux
         self.wait_visibility()
         self.grab_set()
 
diff --git a/theme.py b/theme.py
index b128bc91..bbe62ef5 100644
--- a/theme.py
+++ b/theme.py
@@ -150,7 +150,7 @@ class _Theme:
         # the widget has explicit fg or bg attributes.
         assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
         if not self.defaults:
-            # Can't initialise this til window is created       # Windows, MacOS
+            # Can't initialise this til window is created       # Windows
             self.defaults = {
                 'fg': tk.Label()['foreground'],         # SystemButtonText, systemButtonText
                 'bg': tk.Label()['background'],         # SystemButtonFace, White

From 56a4ae25e0e598666bd0c8c6f8fa72daacdd90a2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 27 Mar 2024 22:21:26 -0400
Subject: [PATCH 19/49] [Nit] Clarify Comments

---
 monitor.py    | 2 +-
 myNotebook.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/monitor.py b/monitor.py
index 5acbcbc4..ba634c51 100644
--- a/monitor.py
+++ b/monitor.py
@@ -439,7 +439,7 @@ class EDLogs(FileSystemEventHandler):
                     new_journal_file = None
 
             if logfile:
-                loghandle.seek(0, SEEK_END)  # required to make macOS notice log change over SMB. # TODO: Do we need this?
+                loghandle.seek(0, SEEK_END)  # required for macOS to notice log change over SMB. TODO: Do we need this?
                 loghandle.seek(log_pos, SEEK_SET)  # reset EOF flag # TODO: log_pos reported as possibly unbound
                 for line in loghandle:
                     # Paranoia check to see if we're shutting down
diff --git a/myNotebook.py b/myNotebook.py
index 528664ba..64624550 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -57,7 +57,7 @@ class Label(tk.Label):
         super().__init__(master, **kw)
 
 
-class Entry(ttk.Entry):  # type: ignore
+class Entry(ttk.Entry):
     """Custom t(t)k.Entry class to fix some display issues."""
 
     # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later.

From b5a4ee6ed2f60c68cec307e9bcd946d6440fc0b7 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Thu, 28 Mar 2024 10:49:01 -0400
Subject: [PATCH 20/49] [Nit] Cleanup some Flake8

---
 config/__init__.py          |  1 -
 plugins/eddn.py             |  1 -
 protocol.py                 | 36 ++++++++++++++++++------------------
 tests/config/_old_config.py |  6 +++---
 4 files changed, 21 insertions(+), 23 deletions(-)

diff --git a/config/__init__.py b/config/__init__.py
index 99e8fdc6..d85956a7 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -467,7 +467,6 @@ def get_config(*args, **kwargs) -> AbstractConfig:
     :param kwargs: Args to be passed through to implementation.
     :return: Instance of the implementation.
     """
-
     if sys.platform == "win32":  # pragma: sys-platform-win32
         from .windows import WinConfig
         return WinConfig(*args, **kwargs)
diff --git a/plugins/eddn.py b/plugins/eddn.py
index b7133725..6345f579 100644
--- a/plugins/eddn.py
+++ b/plugins/eddn.py
@@ -27,7 +27,6 @@ import os
 import pathlib
 import re
 import sqlite3
-import sys
 import tkinter as tk
 from platform import system
 from textwrap import dedent
diff --git a/protocol.py b/protocol.py
index 0f1c157f..4c9e41ca 100644
--- a/protocol.py
+++ b/protocol.py
@@ -26,6 +26,7 @@ is_wine = False
 
 if sys.platform == 'win32':
     from ctypes import windll  # type: ignore
+
     try:
         if windll.ntdll.wine_get_version:
             is_wine = True
@@ -58,13 +59,13 @@ class GenericProtocolHandler:
             self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
 
 
-if (config.auth_force_edmc_protocol
-      or (
-          sys.platform == 'win32'
-          and getattr(sys, 'frozen', False)
-          and not is_wine
-          and not config.auth_force_localserver
-      )):
+if (config.auth_force_edmc_protocol  # noqa: C901
+        or (
+                sys.platform == 'win32'
+                and getattr(sys, 'frozen', False)
+                and not is_wine
+                and not config.auth_force_localserver
+        )):
     # This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces
     assert sys.platform == 'win32'
     # spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL
@@ -186,11 +187,11 @@ if (config.auth_force_edmc_protocol
         # which we can read out as shown below, and then compare.
 
         target_is_valid = lparam_low == 0 or (
-            GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
+                GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname
         )
 
         topic_is_valid = lparam_high == 0 or (
-            GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
+                GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system'
         )
 
         if target_is_valid and topic_is_valid:
@@ -251,15 +252,15 @@ if (config.auth_force_edmc_protocol
 
             # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
             hwnd = CreateWindowExW(
-                0,                       # dwExStyle
+                0,  # dwExStyle
                 wndclass.lpszClassName,  # lpClassName
-                "DDE Server",            # lpWindowName
-                0,                       # dwStyle
+                "DDE Server",  # lpWindowName
+                0,  # dwStyle
                 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,  # X, Y, nWidth, nHeight
                 self.master.winfo_id(),  # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
-                None,                    # hMenu
-                wndclass.hInstance,      # hInstance
-                None                     # lpParam
+                None,  # hMenu
+                wndclass.hInstance,  # hInstance
+                None  # lpParam
             )
 
             msg = MSG()
@@ -421,10 +422,9 @@ def get_handler_impl() -> Type[GenericProtocolHandler]:
 
     :return: An instantiatable GenericProtocolHandler
     """
-
     if (
-        (sys.platform == 'win32' and config.auth_force_edmc_protocol)
-        or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
+            (sys.platform == 'win32' and config.auth_force_edmc_protocol)
+            or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver)
     ):
         return WindowsProtocolHandler
 
diff --git a/tests/config/_old_config.py b/tests/config/_old_config.py
index 35ae19e3..2b5244b6 100644
--- a/tests/config/_old_config.py
+++ b/tests/config/_old_config.py
@@ -5,8 +5,8 @@ import numbers
 import sys
 import warnings
 from configparser import NoOptionError
-from os import getenv, makedirs, mkdir, pardir
-from os.path import dirname, expanduser, isdir, join, normpath
+from os import getenv, makedirs, mkdir
+from os.path import dirname, expanduser, isdir, join
 from typing import TYPE_CHECKING
 from config import applongname, appname, update_interval
 from EDMCLogging import get_main_logger
@@ -109,7 +109,7 @@ class OldConfig:
     OUT_EDDN_DELAY = 4096
     OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
 
-    if sys.platform == 'win32':
+    if sys.platform == 'win32':  # noqa: C901
 
         def __init__(self):
             self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname)  # type: ignore

From 572f724a71a851b717f1a9938fd21e4fd24682c2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Thu, 28 Mar 2024 12:59:22 -0400
Subject: [PATCH 21/49] [1471] Add PIL to Improve Clipboard

---
 L10n/en.template |  5 ++++-
 build.py         |  1 -
 myNotebook.py    | 22 ++++++++++++++++++----
 requirements.txt |  1 +
 4 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/L10n/en.template b/L10n/en.template
index f5acb377..bf8d022e 100644
--- a/L10n/en.template
+++ b/L10n/en.template
@@ -802,4 +802,7 @@
 "Ships" = "Ships";
 
 /* update.py: Update Available Text; In files: update.py:229; */
-"{NEWVER} is available" = "{NEWVER} is available";
\ No newline at end of file
+"{NEWVER} is available" = "{NEWVER} is available";
+
+/* myNotebook.py: Can't Paste Images or Files in Text; */
+"Cannot paste non-text content." = "Cannot paste non-text content.";
diff --git a/build.py b/build.py
index 1bc96765..2d4ef790 100644
--- a/build.py
+++ b/build.py
@@ -133,7 +133,6 @@ def build() -> None:
                 "distutils",
                 "_markerlib",
                 "optparse",
-                "PIL",
                 "simplejson",
                 "unittest",
                 "doctest",
diff --git a/myNotebook.py b/myNotebook.py
index 63bb7dc8..398b2136 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -14,7 +14,12 @@ from __future__ import annotations
 
 import sys
 import tkinter as tk
-from tkinter import ttk
+from tkinter import ttk, messagebox
+from typing import TYPE_CHECKING
+from PIL import ImageGrab
+
+if TYPE_CHECKING:
+    def _(x: str) -> str: return x
 
 # Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult
 if sys.platform == 'darwin':
@@ -126,13 +131,22 @@ class EntryMenu(ttk.Entry):
 
     def paste(self) -> None:
         """Paste the selected Entry text."""
-        if self.selection_present():
-            self.delete(tk.SEL_FIRST, tk.SEL_LAST)
         try:
+            # Attempt to grab an image from the clipboard (apprently also works for files)
+            img = ImageGrab.grabclipboard()
+            if img:
+                # Hijack existing translation, yes it doesn't exactly match here.
+                # LANG: Generic error prefix - following text is from Frontier auth service;
+                messagebox.showwarning(_('Error'),
+                                       _('Cannot paste non-text content.'))  # LANG: Can't Paste Images or Files in Text
+                return
             text = self.clipboard_get()
+            if self.selection_present() and text:
+                self.delete(tk.SEL_FIRST, tk.SEL_LAST)
             self.insert(tk.INSERT, text)
         except tk.TclError:
-            pass  # No text in clipboard or clipboard is not text
+            # No text in clipboard or clipboard is not text
+            pass
 
 
 class Entry(sys.platform == 'darwin' and tk.Entry or EntryMenu or ttk.Entry):  # type: ignore
diff --git a/requirements.txt b/requirements.txt
index 398041a1..e0e6138f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
 certifi==2023.11.17
 requests==2.31.0
+pillow==10.2.0
 # requests depends on this now ?
 charset-normalizer==2.1.1
 

From 0d9607b4f89ee2d351542fb2d69b156e4bce9b3c Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 30 Mar 2024 16:02:54 -0400
Subject: [PATCH 22/49] [Minor] Add Missing Future Annotation

---
 config/linux.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/config/linux.py b/config/linux.py
index 73100800..51a40626 100644
--- a/config/linux.py
+++ b/config/linux.py
@@ -5,6 +5,8 @@ Copyright (c) EDCD, All Rights Reserved
 Licensed under the GNU General Public License.
 See LICENSE file.
 """
+from __future__ import annotations
+
 import os
 import pathlib
 import sys

From ecf0ceb612f474e1f54bf2c28fa603708db960d1 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 1 Apr 2024 17:08:13 +0000
Subject: [PATCH 23/49] build(deps-dev): bump mypy from 1.8.0 to 1.9.0

Bumps [mypy](https://github.com/python/mypy) from 1.8.0 to 1.9.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.8.0...1.9.0)

---
updated-dependencies:
- dependency-name: mypy
  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 73ab5465..dd59b4f5 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -18,7 +18,7 @@ flake8-noqa==1.4.0
 flake8-polyfill==1.0.2
 flake8-use-fstring==1.4
 
-mypy==1.8.0
+mypy==1.9.0
 pep8-naming==0.13.3
 safety==2.3.5
 types-requests==2.31.0.20240125

From 2ea942aec06e2a420ab801751e61db2a4d21dc65 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 1 Apr 2024 17:08:16 +0000
Subject: [PATCH 24/49] build(deps-dev): bump autopep8 from 2.0.4 to 2.1.0

Bumps [autopep8](https://github.com/hhatto/autopep8) from 2.0.4 to 2.1.0.
- [Release notes](https://github.com/hhatto/autopep8/releases)
- [Commits](https://github.com/hhatto/autopep8/compare/v2.0.4...v2.1.0)

---
updated-dependencies:
- dependency-name: autopep8
  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 73ab5465..83e68822 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -25,7 +25,7 @@ types-requests==2.31.0.20240125
 types-pkg-resources==0.1.3
 
 # Code formatting tools
-autopep8==2.0.4
+autopep8==2.1.0
 
 # Git pre-commit checking
 pre-commit==3.6.2

From 854dd4072e221d066add0a136a3435e30d0dc0ba Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 1 Apr 2024 17:08:19 +0000
Subject: [PATCH 25/49] build(deps): bump certifi from 2023.11.17 to 2024.2.2

Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.11.17 to 2024.2.2.
- [Commits](https://github.com/certifi/python-certifi/compare/2023.11.17...2024.02.02)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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 398041a1..75b7fc01 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-certifi==2023.11.17
+certifi==2024.2.2
 requests==2.31.0
 # requests depends on this now ?
 charset-normalizer==2.1.1

From 25d52eacf6dcf603bbdb36e9443023756342c4fc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 1 Apr 2024 17:29:12 +0000
Subject: [PATCH 26/49] build(deps): bump softprops/action-gh-release from 1 to
 2

Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 .github/workflows/windows-build.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml
index 50962bbd..7bc60136 100644
--- a/.github/workflows/windows-build.yml
+++ b/.github/workflows/windows-build.yml
@@ -155,7 +155,7 @@ jobs:
         run: sha256sum EDMarketConnector_Installer_*.exe EDMarketConnector-release-*.{zip,tar.gz} > ./hashes.sum
 
       - name: Create Draft Release
-        uses: "softprops/action-gh-release@v1"
+        uses: "softprops/action-gh-release@v2"
         with:
           token: "${{secrets.GITHUB_TOKEN}}"
           draft: true

From d8d8540ceeea7b2c9beb57843df4ce3be136e7e2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:16:25 -0400
Subject: [PATCH 27/49] [2186] Resolve Some Merge Issues From EntryMenu

---
 myNotebook.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 60 insertions(+), 3 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index 64624550..7379aa5a 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -57,12 +57,69 @@ class Label(tk.Label):
         super().__init__(master, **kw)
 
 
-class Entry(ttk.Entry):
+class EntryMenu(ttk.Entry):
+    """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
+
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+
+        self.menu = tk.Menu(self, tearoff=False)
+        self.menu.add_command(label="Copy", command=self.copy)
+        self.menu.add_command(label="Cut", command=self.cut)
+        self.menu.add_separator()
+        self.menu.add_command(label="Paste", command=self.paste)
+        self.menu.add_separator()
+        self.menu.add_command(label="Select All", command=self.select_all)
+
+        self.bind("<Button-3>", self.display_popup)
+
+    def display_popup(self, event: tk.Event) -> None:
+        """Display the menu popup."""
+        self.menu.post(event.x_root, event.y_root)
+
+    def select_all(self) -> None:
+        """Select all the text within the Entry."""
+        self.selection_range(0, tk.END)
+        self.focus_set()
+
+    def copy(self) -> None:
+        """Copy the selected Entry text."""
+        if self.selection_present():
+            self.clipboard_clear()
+            self.clipboard_append(self.selection_get())
+
+    def cut(self) -> None:
+        """Cut the selected Entry text."""
+        if self.selection_present():
+            self.copy()
+            self.delete(tk.SEL_FIRST, tk.SEL_LAST)
+
+    def paste(self) -> None:
+        """Paste the selected Entry text."""
+        try:
+            # Attempt to grab an image from the clipboard (apprently also works for files)
+            img = ImageGrab.grabclipboard()
+            if img:
+                # Hijack existing translation, yes it doesn't exactly match here.
+                # LANG: Generic error prefix - following text is from Frontier auth service;
+                messagebox.showwarning(_('Error'),
+                                       _('Cannot paste non-text content.'))  # LANG: Can't Paste Images or Files in Text
+                return
+            text = self.clipboard_get()
+            if self.selection_present() and text:
+                self.delete(tk.SEL_FIRST, tk.SEL_LAST)
+            self.insert(tk.INSERT, text)
+        except tk.TclError:
+            # No text in clipboard or clipboard is not text
+            pass
+
+
+class Entry(ttk.Entry or EntryMenu):
     """Custom t(t)k.Entry class to fix some display issues."""
 
-    # DEPRECATED: Migrate to ttk.Entry. Will remove in 5.12 or later.
+    # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
-        super().__init__(master, **kw)
+        EntryMenu.__init__(self, master, **kw)
 
 
 class Button(tk.Button or ttk.Button):  # type: ignore

From d00226f9e3f8dbbf211851433bb501a984334658 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:20:05 -0400
Subject: [PATCH 28/49] [2186] Additional Documentations

---
 .flake8          | 1 -
 .mypy.ini        | 1 -
 Contributing.md  | 2 +-
 pyproject.toml   | 3 ---
 requirements.txt | 3 ---
 5 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/.flake8 b/.flake8
index bdb25ca4..179e00ec 100644
--- a/.flake8
+++ b/.flake8
@@ -7,7 +7,6 @@ exclude =
     FDevIDs/
     venv/
     .venv/
-    hotkey/darwin.py  # FIXME: Check under macOS VM at some point
 
 # Show exactly where in a line the error happened
 #show-source = True
diff --git a/.mypy.ini b/.mypy.ini
index 75f4ccdb..9ac2f227 100644
--- a/.mypy.ini
+++ b/.mypy.ini
@@ -6,5 +6,4 @@ scripts_are_modules = True
 ;   `<var> = <value>`
 ; i.e. no typing info.
 check_untyped_defs = True
-; platform = darwin
 explicit_package_bases = True
diff --git a/Contributing.md b/Contributing.md
index 5f7bc158..f021f1a3 100644
--- a/Contributing.md
+++ b/Contributing.md
@@ -679,7 +679,7 @@ the following does not work:
 
 ```py
 from sys import platform
-if platform == 'darwin':
+if platform == 'win32':
   ...
 ```
 
diff --git a/pyproject.toml b/pyproject.toml
index e32ad617..b98fbf4f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,6 +23,3 @@ sys-platform-not-darwin = "sys_platform == 'darwin'"
 sys-platform-linux = "sys_platform != 'linux'"
 sys-platform-not-linux = "sys_platform == 'linux'"
 sys-platform-not-known = "sys_platform in ('darwin', 'linux', 'win32')"
-
-[tool.pyright]
-# pythonPlatform = 'Darwin'
diff --git a/requirements.txt b/requirements.txt
index e9d4f58d..c9239fdc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,6 +10,3 @@ infi.systray==0.1.12; sys_platform == 'win32'
 # argh==0.26.2 watchdog dep
 # pyyaml==5.3.1 watchdog dep
 semantic-version==2.10.0
-
-# Base requirement for MacOS
-pyobjc; sys_platform == 'darwin'

From fbdc44139077db712fa39b6d4ae60fc3013e1fc2 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:28:08 -0400
Subject: [PATCH 29/49] [Minor] Return Visual Padding

Just makes it nicer to read.
---
 protocol.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/protocol.py b/protocol.py
index 4c9e41ca..b052ebae 100644
--- a/protocol.py
+++ b/protocol.py
@@ -254,12 +254,12 @@ if (config.auth_force_edmc_protocol  # noqa: C901
             hwnd = CreateWindowExW(
                 0,  # dwExStyle
                 wndclass.lpszClassName,  # lpClassName
-                "DDE Server",  # lpWindowName
-                0,  # dwStyle
+                "DDE Server",            # lpWindowName
+                0,                       # dwStyle
                 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,  # X, Y, nWidth, nHeight
                 self.master.winfo_id(),  # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
-                None,  # hMenu
-                wndclass.hInstance,  # hInstance
+                None,                    # hMenu
+                wndclass.hInstance,      # hInstance
                 None  # lpParam
             )
 

From 3a8227a8741ef2856472d300490ab2e30c2c7d57 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:33:22 -0400
Subject: [PATCH 30/49] [2186] Correct Logic

---
 EDMarketConnector.py | 8 +++-----
 prefs.py             | 6 +++---
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/EDMarketConnector.py b/EDMarketConnector.py
index 967488e0..3ab97eb5 100755
--- a/EDMarketConnector.py
+++ b/EDMarketConnector.py
@@ -1750,8 +1750,7 @@ class AppWindow:
 
             # position over parent
             # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-            if parent.winfo_rooty() > 0:
-                self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
+            self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}')
 
             # remove decoration
             if sys.platform == 'win32':
@@ -1870,9 +1869,8 @@ class AppWindow:
         config.set_shutdown()  # Signal we're in shutdown now.
 
         # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-        if self.w.winfo_rooty() > 0:
-            x, y = self.w.geometry().split('+')[1:3]  # e.g. '212x170+2881+1267'
-            config.set('geometry', f'+{x}+{y}')
+        x, y = self.w.geometry().split('+')[1:3]  # e.g. '212x170+2881+1267'
+        config.set('geometry', f'+{x}+{y}')
 
         # Let the user know we're shutting down.
         # LANG: The application is shutting down
diff --git a/prefs.py b/prefs.py
index 5b045b1a..9e0459b6 100644
--- a/prefs.py
+++ b/prefs.py
@@ -230,9 +230,9 @@ class PreferencesDialog(tk.Toplevel):
             self.transient(parent)
 
         # position over parent
-        if parent.winfo_rooty() > 0:  # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
-            # TODO this is fixed supposedly.
-            self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
+        # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7
+        # TODO this is fixed supposedly.
+        self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}')
 
         # remove decoration
         if sys.platform == 'win32':

From e0ef9b52c3cdc5aedc9982c736a7169bd3a5d2f8 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:45:07 -0400
Subject: [PATCH 31/49] [2186] Additional Tweaks

---
 myNotebook.py | 32 ++++++++++++++++++--------------
 1 file changed, 18 insertions(+), 14 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index 44488b8a..e8f8779b 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -30,15 +30,16 @@ class Notebook(ttk.Notebook):
 
         super().__init__(master, **kw)
         style = ttk.Style()
-        style.configure('nb.TFrame',                          background=PAGEBG)
-        style.configure('nb.TButton',                         background=PAGEBG)
-        style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
-        style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
-        style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
+        if sys.platform == 'win32':
+            style.configure('nb.TFrame',                          background=PAGEBG)
+            style.configure('nb.TButton',                         background=PAGEBG)
+            style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
+            style.configure('nb.TMenubutton',  foreground=PAGEFG, background=PAGEBG)
+            style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
         self.grid(padx=10, pady=10, sticky=tk.NSEW)
 
 
-class Frame(tk.Frame or ttk.Frame):  # type: ignore
+class Frame(ttk.Frame):
     """Custom t(t)k.Frame class to fix some display issues."""
 
     def __init__(self, master: ttk.Notebook | None = None, **kw):
@@ -127,7 +128,7 @@ class Entry(ttk.Entry or EntryMenu):
         EntryMenu.__init__(self, master, **kw)
 
 
-class Button(tk.Button or ttk.Button):  # type: ignore
+class Button(ttk.Button):  # type: ignore
     """Custom t(t)k.Button class to fix some display issues."""
 
     # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later.
@@ -138,7 +139,7 @@ class Button(tk.Button or ttk.Button):  # type: ignore
             ttk.Button.__init__(self, master, **kw)
 
 
-class ColoredButton(tk.Label or tk.Button):  # type: ignore
+class ColoredButton(tk.Button):  # type: ignore
     """Custom t(t)k.ColoredButton class to fix some display issues."""
 
     # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later.
@@ -166,11 +167,14 @@ class OptionMenu(ttk.OptionMenu):
     """Custom ttk.OptionMenu class to fix some display issues."""
 
     def __init__(self, master, variable, default=None, *values, **kw):
-        style = 'nb.TMenubutton' if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
-        menu_background = PAGEBG if sys.platform == 'win32' else ttk.Style().lookup('TMenu', 'background')
+        if sys.platform == 'win32':
+            # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
+            ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
+            self['menu'].configure(background=PAGEBG)
+        else:
+            ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
+            self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
 
-        super().__init__(master, variable, default, *values, style=style, **kw)
-        self['menu'].configure(background=menu_background)
-
-        for i in range(self['menu'].index('end') + 1):
+        # Workaround for https://bugs.python.org/issue25684
+        for i in range(0, self['menu'].index('end') + 1):
             self['menu'].entryconfig(i, variable=variable)

From 3c6ea3c9328a95e2a82fa276a2d1e3865c235f36 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Fri, 5 Apr 2024 17:50:30 -0400
Subject: [PATCH 32/49] [Minor] Comment Clarify

---
 update.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/update.py b/update.py
index 346aff63..991558d6 100644
--- a/update.py
+++ b/update.py
@@ -161,7 +161,7 @@ class Updater:
 
             return None
 
-        # For *these* purposes anything else is the same as 'windows', as
+        # For *these* purposes all systems are the same as 'windows', as
         # non-win32 would be running from source.
         sparkle_platform = 'windows'
 

From c14bd826d08daee4509cf2bf2b184245d7f54695 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 6 Apr 2024 16:52:45 -0400
Subject: [PATCH 33/49] [Minor] Additional Visual Padding Fixes

---
 protocol.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/protocol.py b/protocol.py
index b052ebae..1fb88595 100644
--- a/protocol.py
+++ b/protocol.py
@@ -252,7 +252,7 @@ if (config.auth_force_edmc_protocol  # noqa: C901
 
             # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
             hwnd = CreateWindowExW(
-                0,  # dwExStyle
+                0,                       # dwExStyle
                 wndclass.lpszClassName,  # lpClassName
                 "DDE Server",            # lpWindowName
                 0,                       # dwStyle
@@ -260,7 +260,7 @@ if (config.auth_force_edmc_protocol  # noqa: C901
                 self.master.winfo_id(),  # hWndParent # Don't use HWND_MESSAGE since the window won't get DDE broadcasts
                 None,                    # hMenu
                 wndclass.hInstance,      # hInstance
-                None  # lpParam
+                None                     # lpParam
             )
 
             msg = MSG()

From d9c7a791553f12489ed47f74f6275b533ca7ca22 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 6 Apr 2024 16:59:49 -0400
Subject: [PATCH 34/49] [Minor] Update Type Hintings

No Content Changes, Shuts Up MyPy
---
 docs/examples/click_counter/load.py | 4 ++--
 myNotebook.py                       | 2 +-
 plug.py                             | 2 +-
 plugins/coriolis.py                 | 2 +-
 plugins/eddn.py                     | 4 ++--
 plugins/edsm.py                     | 2 +-
 plugins/inara.py                    | 2 +-
 prefs.py                            | 2 +-
 8 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py
index f7f23837..a3b8cac6 100644
--- a/docs/examples/click_counter/load.py
+++ b/docs/examples/click_counter/load.py
@@ -49,7 +49,7 @@ class ClickCounter:
         """
         self.on_preferences_closed("", False)  # Save our prefs
 
-    def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None:
+    def setup_preferences(self, parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None:
         """
         setup_preferences is called by plugin_prefs below.
 
@@ -128,7 +128,7 @@ def plugin_stop() -> None:
     return cc.on_unload()
 
 
-def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> tk.Frame | None:
+def plugin_prefs(parent: nb.Notebook, cmdr: str, is_beta: bool) -> nb.Frame | None:
     """
     Handle preferences tab for the plugin.
 
diff --git a/myNotebook.py b/myNotebook.py
index e8f8779b..0eeb96a4 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -120,7 +120,7 @@ class EntryMenu(ttk.Entry):
             pass
 
 
-class Entry(ttk.Entry or EntryMenu):
+class Entry(ttk.Entry or EntryMenu):  # type: ignore
     """Custom t(t)k.Entry class to fix some display issues."""
 
     # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later.
diff --git a/plug.py b/plug.py
index 15e7cfaa..dad08bc6 100644
--- a/plug.py
+++ b/plug.py
@@ -126,7 +126,7 @@ class Plugin:
 
         return None
 
-    def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame | None:
+    def get_prefs(self, parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame | None:
         """
         If the plugin provides a prefs frame, create and return it.
 
diff --git a/plugins/coriolis.py b/plugins/coriolis.py
index a97eb6c6..cc7e965a 100644
--- a/plugins/coriolis.py
+++ b/plugins/coriolis.py
@@ -84,7 +84,7 @@ def plugin_start3(path: str) -> str:
     return 'Coriolis'
 
 
-def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
+def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame:
     """Set up plugin preferences."""
     PADX = 10  # noqa: N806
     PADY = 1  # noqa: N806
diff --git a/plugins/eddn.py b/plugins/eddn.py
index c5a978db..9504d1ab 100644
--- a/plugins/eddn.py
+++ b/plugins/eddn.py
@@ -1898,7 +1898,7 @@ class EDDN:
         :param cmdr: the commander under which this upload is made
         :param is_beta: whether or not we are in beta mode
         :param entry: the journal entry to send
-
+        ___
         Example:
         {
             "timestamp":"2022-06-10T10:09:41Z",
@@ -1932,7 +1932,7 @@ class EDDN:
         :param cmdr: the commander under which this upload is made
         :param is_beta: whether or not we are in beta mode
         :param entry: the journal entry to send
-
+        ___
         Example:
         {
             "timestamp":"2023-10-01T14:56:34Z",
diff --git a/plugins/edsm.py b/plugins/edsm.py
index 146dd5ab..55aae7b8 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -279,7 +279,7 @@ def toggle_password_visibility():
         this.apikey.config(show="*")  # type: ignore
 
 
-def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
+def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Frame:
     """
     Plugin preferences setup hook.
 
diff --git a/plugins/inara.py b/plugins/inara.py
index d96e05ef..29bfaaf2 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -244,7 +244,7 @@ def toggle_password_visibility():
         this.apikey.config(show="*")
 
 
-def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> tk.Frame:
+def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
     """Plugin Preferences UI hook."""
     PADX = 10  # noqa: N806
     BUTTONX = 12  # noqa: N806  # indent Checkbuttons and Radiobuttons
diff --git a/prefs.py b/prefs.py
index 9e0459b6..5e0f946b 100644
--- a/prefs.py
+++ b/prefs.py
@@ -376,7 +376,7 @@ class PreferencesDialog(tk.Toplevel):
         )
         self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get())
 
-        nb.Frame(output_frame).grid(row=row.get())  # bottom spacer # TODO: does nothing?
+        nb.Frame(output_frame).grid(row=row.get())  # type: ignore # bottom spacer # TODO: does nothing?
 
         # LANG: Label for 'Output' Settings/Preferences tab
         root_notebook.add(output_frame, text=_('Output'))  # Tab heading in settings

From 4cbc86e650d04f696b58332dd27f1b502f312437 Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Thu, 11 Apr 2024 12:25:11 +0000
Subject: [PATCH 35/49] updating submodules

---
 FDevIDs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/FDevIDs b/FDevIDs
index 7205c793..7cffab3d 160000
--- a/FDevIDs
+++ b/FDevIDs
@@ -1 +1 @@
-Subproject commit 7205c79331f42c1a28b757b27467f79ff106716b
+Subproject commit 7cffab3d913b788f981923687203399c22cf358f

From dd5e3812a759c1d3186df277e249d0a9e6216e76 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 13 Apr 2024 14:44:44 -0400
Subject: [PATCH 36/49] [2186] Refine macOS to preserve ContextMenu

---
 docs/examples/click_counter/load.py |  3 +--
 myNotebook.py                       | 23 +++++++++++------------
 plugins/coriolis.py                 | 15 +++++++--------
 plugins/edsm.py                     |  8 ++++----
 plugins/inara.py                    |  4 ++--
 prefs.py                            |  2 --
 requirements.txt                    |  4 ++--
 7 files changed, 27 insertions(+), 32 deletions(-)

diff --git a/docs/examples/click_counter/load.py b/docs/examples/click_counter/load.py
index a3b8cac6..3620e159 100644
--- a/docs/examples/click_counter/load.py
+++ b/docs/examples/click_counter/load.py
@@ -7,7 +7,6 @@ from __future__ import annotations
 
 import logging
 import tkinter as tk
-from tkinter import ttk
 
 import myNotebook as nb  # noqa: N813
 from config import appname, config
@@ -65,7 +64,7 @@ class ClickCounter:
 
         # setup our config in a "Click Count: number"
         nb.Label(frame, text='Click Count').grid(row=current_row)
-        ttk.Entry(frame, textvariable=self.click_count).grid(row=current_row, column=1)
+        nb.EntryMenu(frame, textvariable=self.click_count).grid(row=current_row, column=1)
         current_row += 1  # Always increment our row counter, makes for far easier tkinter design.
         return frame
 
diff --git a/myNotebook.py b/myNotebook.py
index 0eeb96a4..2442acbe 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -40,7 +40,7 @@ class Notebook(ttk.Notebook):
 
 
 class Frame(ttk.Frame):
-    """Custom t(t)k.Frame class to fix some display issues."""
+    """Custom ttk.Frame class to fix some display issues."""
 
     def __init__(self, master: ttk.Notebook | None = None, **kw):
         if sys.platform == 'win32':
@@ -120,18 +120,17 @@ class EntryMenu(ttk.Entry):
             pass
 
 
-class Entry(ttk.Entry or EntryMenu):  # type: ignore
-    """Custom t(t)k.Entry class to fix some display issues."""
+class Entry(EntryMenu or ttk.Entry):  # type: ignore
+    """Custom ttk.Entry class to fix some display issues."""
 
-    # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later.
+    # DEPRECATED: Migrate to EntryMenu. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
         EntryMenu.__init__(self, master, **kw)
 
 
-class Button(ttk.Button):  # type: ignore
-    """Custom t(t)k.Button class to fix some display issues."""
+class Button(ttk.Button):
+    """Custom ttk.Button class to fix some display issues."""
 
-    # DEPRECATED: Migrate to ttk.Button. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
         if sys.platform == 'win32':
             ttk.Button.__init__(self, master, style='nb.TButton', **kw)
@@ -139,8 +138,8 @@ class Button(ttk.Button):  # type: ignore
             ttk.Button.__init__(self, master, **kw)
 
 
-class ColoredButton(tk.Button):  # type: ignore
-    """Custom t(t)k.ColoredButton class to fix some display issues."""
+class ColoredButton(tk.Button):
+    """Custom tk.Button class to fix some display issues."""
 
     # DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later.
     def __init__(self, master: ttk.Frame | None = None, **kw):
@@ -148,15 +147,15 @@ class ColoredButton(tk.Button):  # type: ignore
 
 
 class Checkbutton(ttk.Checkbutton):
-    """Custom t(t)k.Checkbutton class to fix some display issues."""
+    """Custom ttk.Checkbutton class to fix some display issues."""
 
-    def __init__(self, master=None, **kw):
+    def __init__(self, master: ttk.Frame | None = None, **kw):
         style = 'nb.TCheckbutton' if sys.platform == 'win32' else None
         super().__init__(master, style=style, **kw)  # type: ignore
 
 
 class Radiobutton(ttk.Radiobutton):
-    """Custom t(t)k.Radiobutton class to fix some display issues."""
+    """Custom ttk.Radiobutton class to fix some display issues."""
 
     def __init__(self, master: ttk.Frame | None = None, **kw):
         style = 'nb.TRadiobutton' if sys.platform == 'win32' else None
diff --git a/plugins/coriolis.py b/plugins/coriolis.py
index cc7e965a..07b4ac9f 100644
--- a/plugins/coriolis.py
+++ b/plugins/coriolis.py
@@ -106,25 +106,24 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
 
     # LANG: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Normal URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
-    ttk.Entry(conf_frame,
-              textvariable=coriolis_config.normal_textvar).grid(
+    nb.EntryMenu(conf_frame, textvariable=coriolis_config.normal_textvar).grid(
                 sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
             )
     # LANG: Generic 'Reset' button label
-    ttk.Button(conf_frame, text=_("Reset"),
-               command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
+    nb.Button(conf_frame, text=_("Reset"),
+              command=lambda: coriolis_config.normal_textvar.set(value=DEFAULT_NORMAL_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
     cur_row += 1
 
     # LANG: Settings>Coriolis: Label for 'alpha/beta game version' URL
     nb.Label(conf_frame, text=_('Beta URL')).grid(sticky=tk.W, row=cur_row, column=0, padx=PADX, pady=PADY)
-    ttk.Entry(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
-        sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
+    nb.EntryMenu(conf_frame, textvariable=coriolis_config.beta_textvar).grid(
+                 sticky=tk.EW, row=cur_row, column=1, padx=PADX, pady=BOXY
     )
     # LANG: Generic 'Reset' button label
-    ttk.Button(conf_frame, text=_('Reset'),
-               command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
+    nb.Button(conf_frame, text=_('Reset'),
+              command=lambda: coriolis_config.beta_textvar.set(value=DEFAULT_BETA_URL)).grid(
         sticky=tk.W, row=cur_row, column=2, padx=PADX, pady=0
     )
     cur_row += 1
diff --git a/plugins/edsm.py b/plugins/edsm.py
index 55aae7b8..b2a8035f 100644
--- a/plugins/edsm.py
+++ b/plugins/edsm.py
@@ -113,10 +113,10 @@ class This:
         self.cmdr_text: nb.Label | None = None
 
         self.user_label: nb.Label | None = None
-        self.user: ttk.Entry | None = None
+        self.user: nb.EntryMenu | None = None
 
         self.apikey_label: nb.Label | None = None
-        self.apikey: ttk.Entry | None = None
+        self.apikey: nb.EntryMenu | None = None
 
 
 this = This()
@@ -345,14 +345,14 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> nb.Fr
     # LANG: EDSM Commander name label in EDSM settings
     this.user_label = nb.Label(frame, text=_('Commander Name'))
     this.user_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.user = ttk.Entry(frame)
+    this.user = nb.EntryMenu(frame)
     this.user.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
 
     cur_row += 1
     # LANG: EDSM API key label
     this.apikey_label = nb.Label(frame, text=_('API Key'))
     this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.apikey = ttk.Entry(frame, show="*", width=50)
+    this.apikey = nb.EntryMenu(frame, show="*", width=50)
     this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
     cur_row += 1
 
diff --git a/plugins/inara.py b/plugins/inara.py
index 29bfaaf2..810f7523 100644
--- a/plugins/inara.py
+++ b/plugins/inara.py
@@ -125,7 +125,7 @@ class This:
         self.log: 'tk.IntVar'
         self.log_button: nb.Checkbutton
         self.label: HyperlinkLabel
-        self.apikey: ttk.Entry
+        self.apikey: nb.EntryMenu
         self.apikey_label: tk.Label
 
         self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
@@ -292,7 +292,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: str, is_beta: bool) -> nb.Frame:
     # LANG: Inara API key label
     this.apikey_label = nb.Label(frame, text=_('API Key'))  # Inara setting
     this.apikey_label.grid(row=cur_row, padx=PADX, pady=PADY, sticky=tk.W)
-    this.apikey = ttk.Entry(frame, show="*", width=50)
+    this.apikey = nb.EntryMenu(frame, show="*", width=50)
     this.apikey.grid(row=cur_row, column=1, padx=PADX, pady=BOXY, sticky=tk.EW)
     cur_row += 1
 
diff --git a/prefs.py b/prefs.py
index 5e0f946b..285ef0d7 100644
--- a/prefs.py
+++ b/prefs.py
@@ -376,8 +376,6 @@ class PreferencesDialog(tk.Toplevel):
         )
         self.outbutton.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=row.get())
 
-        nb.Frame(output_frame).grid(row=row.get())  # type: ignore # bottom spacer # TODO: does nothing?
-
         # LANG: Label for 'Output' Settings/Preferences tab
         root_notebook.add(output_frame, text=_('Output'))  # Tab heading in settings
 
diff --git a/requirements.txt b/requirements.txt
index c9239fdc..75ea4041 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,8 @@
 certifi==2024.2.2
 requests==2.31.0
-pillow==10.2.0
+pillow==10.3.0
 # requests depends on this now ?
-charset-normalizer==2.1.1
+charset-normalizer==3.3.2
 
 watchdog==3.0.0
 # Commented out because this doesn't package well with py2exe

From 0410806152fbe324867e9bc1258b3211b409344a Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sun, 14 Apr 2024 18:07:30 -0400
Subject: [PATCH 37/49] [2199] Add Broader Exception

---
 killswitch.py | 4 ++--
 myNotebook.py | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/killswitch.py b/killswitch.py
index 9cc407a6..89a55292 100644
--- a/killswitch.py
+++ b/killswitch.py
@@ -361,8 +361,8 @@ def fetch_kill_switches(target=DEFAULT_KILLSWITCH_URL) -> KillSwitchJSONFile | N
         logger.warning(f"Failed to get kill switches, data was invalid: {e}")
         return None
 
-    except (requests.exceptions.BaseHTTPError, requests.exceptions.ConnectionError) as e:  # type: ignore
-        logger.warning(f"unable to connect to {target!r}: {e}")
+    except requests.exceptions.RequestException as requ_err:
+        logger.warning(f"unable to connect to {target!r}: {requ_err}")
         return None
 
     return data
diff --git a/myNotebook.py b/myNotebook.py
index 0eeb96a4..88b4aec5 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -63,11 +63,11 @@ class Label(tk.Label):
         super().__init__(master, **kw)
 
 
-class EntryMenu(ttk.Entry):
+class EntryMenu:
     """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
 
     def __init__(self, *args, **kwargs) -> None:
-        super().__init__(*args, **kwargs)
+        ttk.Entry.__init__(self, *args, **kwargs)
 
         self.menu = tk.Menu(self, tearoff=False)
         self.menu.add_command(label="Copy", command=self.copy)
@@ -120,7 +120,7 @@ class EntryMenu(ttk.Entry):
             pass
 
 
-class Entry(ttk.Entry or EntryMenu):  # type: ignore
+class Entry(ttk.Entry, EntryMenu):
     """Custom t(t)k.Entry class to fix some display issues."""
 
     # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later.

From fbeeded28046aac2985dc468b757ccf82711cf92 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sun, 14 Apr 2024 20:17:08 -0400
Subject: [PATCH 38/49] [2202] Add a number of missing modules

This will need to be basically speedran into live.
---
 edmc_data.py  | 31 ++++++++++++++++++++++++-------
 outfitting.py | 17 ++++++++++++++++-
 2 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/edmc_data.py b/edmc_data.py
index 7be76096..d76badc6 100644
--- a/edmc_data.py
+++ b/edmc_data.py
@@ -69,6 +69,8 @@ outfitting_weapon_map = {
     'advancedtorppylon':                 'Torpedo Pylon',
     'atdumbfiremissile':                 'AX Missile Rack',
     'atmulticannon':                     'AX Multi-Cannon',
+    ('atmulticannon', 'v2'):             'Enhanced AX Multi-Cannon',
+    ('atdumbfiremissile', 'v2'):         'Enhanced AX Missile Rack',
     'basicmissilerack':                  'Seeker Missile Rack',
     'beamlaser':                         'Beam Laser',
     ('beamlaser', 'heat'):               'Retributor Beam Laser',
@@ -88,6 +90,8 @@ outfitting_weapon_map = {
     'mining_abrblstr':                   'Abrasion Blaster',
     'mining_seismchrgwarhd':             'Seismic Charge Launcher',
     'mining_subsurfdispmisle':           'Sub-Surface Displacement Missile',
+    'human_extraction':                  'Sub-Surface Extraction Missile',
+    'atventdisruptorpylon':              'Guardian Nanite Torpedo Pylon',
     'mininglaser':                       'Mining Laser',
     ('mininglaser', 'advanced'):         'Mining Lance Beam Laser',
     'multicannon':                       'Multi-Cannon',
@@ -266,6 +270,11 @@ outfitting_weaponrating_map = {
     'hpt_slugshot_turret_medium':                'D',
     'hpt_slugshot_turret_large':                 'C',
     'hpt_xenoscannermk2_basic_tiny':             '?',
+    'hpt_atmulticannon_gimbal_large':            'C',
+    'hpt_atmulticannon_gimbal_medium':           'E',
+    'hpt_human_extraction_fixed_medium':         'B',
+    'hpt_atventdisruptorpylon_fixed_medium':     'I',
+    'hpt_atventdisruptorpylon_fixed_large':      'I',
 }
 
 # Old standard weapon variants
@@ -278,13 +287,14 @@ outfitting_weaponoldvariant_map = {
 }
 
 outfitting_countermeasure_map = {
-    'antiunknownshutdown':        ('Shutdown Field Neutraliser', 'F'),
-    'chafflauncher':              ('Chaff Launcher', 'I'),
-    'electroniccountermeasure':   ('Electronic Countermeasure', 'F'),
-    'heatsinklauncher':           ('Heat Sink Launcher', 'I'),
-    'plasmapointdefence':         ('Point Defence', 'I'),
-    'xenoscanner':                ('Xeno Scanner', 'E'),
-    'xenoscannermk2':             ('Unknown Xeno Scanner Mk II', '?'),
+    'antiunknownshutdown':          ('Shutdown Field Neutraliser', 'F'),
+    ('antiunknownshutdown', 'v2'):  ('Thargoid Pulse Neutraliser', 'E'),
+    'chafflauncher':                ('Chaff Launcher', 'I'),
+    'electroniccountermeasure':     ('Electronic Countermeasure', 'F'),
+    'heatsinklauncher':             ('Heat Sink Launcher', 'I'),
+    'plasmapointdefence':           ('Point Defence', 'I'),
+    'xenoscanner':                  ('Xeno Scanner', 'E'),
+    'xenoscannermk2':               ('Unknown Xeno Scanner Mk II', '?'),
 }
 
 outfitting_utility_map = {
@@ -347,6 +357,7 @@ outfitting_standard_map = {
     'guardianpowerdistributor':     'Guardian Hybrid Power Distributor',
     'guardianpowerplant':           'Guardian Hybrid Power Plant',
     'hyperdrive':                   'Frame Shift Drive',
+    ('hyperdrive', 'overcharge'):   'Frame Shift Drive',
     'lifesupport':                  'Life Support',
     # 'planetapproachsuite':        handled separately
     'powerdistributor':             'Power Distributor',
@@ -376,6 +387,11 @@ outfitting_internal_map = {
     'refinery':                     'Refinery',
     'recon':                        'Recon Limpet Controller',
     'repair':                       'Repair Limpet Controller',
+    'rescue':                       'Rescue Limpet Controller',
+    'mining':                       'Mining Multi Limpet Controller',
+    'xeno':                         'Xeno Multi Limpet Controller',
+    'operations':                   'Operations Multi Limpet Controller',
+    'universal':                    'Universal Multi Limpet Controller',
     'repairer':                     'Auto Field-Maintenance Unit',
     'resourcesiphon':               'Hatch Breaker Limpet Controller',
     'shieldcellbank':               'Shield Cell Bank',
@@ -383,6 +399,7 @@ outfitting_internal_map = {
     ('shieldgenerator', 'fast'):    'Bi-Weave Shield Generator',
     ('shieldgenerator', 'strong'):  'Prismatic Shield Generator',
     'unkvesselresearch':            'Research Limpet Controller',
+    'expmodulestabiliser':          'Experimental Weapon Stabiliser',
 }
 
 # Dashboard Flags constants
diff --git a/outfitting.py b/outfitting.py
index a1b6b9db..5632687c 100644
--- a/outfitting.py
+++ b/outfitting.py
@@ -98,6 +98,12 @@ def lookup(module, ship_map, entitled=False) -> dict | None:  # noqa: C901, CCR0
     elif not entitled and name[1] == 'planetapproachsuite':
         return None
 
+    # V2 Shutdown Field Neutralizer - Hpt_AntiUnknownShutdown_Tiny_V2
+    elif name[0] == 'hpt' and name[1] in countermeasure_map and len(name) == 4 and name[3] == 'v2':
+        new['category'] = 'utility'
+        new['name'], new['rating'] = countermeasure_map[name[1]]
+        new['class'] = weaponclass_map[name[-2]]
+
     # Countermeasures - e.g. Hpt_PlasmaPointDefence_Turret_Tiny
     elif name[0] == 'hpt' and name[1] in countermeasure_map:
         new['category'] = 'utility'
@@ -179,12 +185,18 @@ def lookup(module, ship_map, entitled=False) -> dict | None:  # noqa: C901, CCR0
         if name[1] == 'dronecontrol':  # e.g. Int_DroneControl_Collection_Size1_Class1
             name.pop(0)
 
+        elif name[1] == 'multidronecontrol':  # e.g. Int_MultiDroneControl_Rescue_Size3_Class3
+            name.pop(0)
+
         elif name[-1] == 'free':  # Starter Sidewinder or Freagle modules - just treat them like vanilla modules
             name.pop()
 
         if name[1] in standard_map:  # e.g. Int_Engine_Size2_Class1, Int_ShieldGenerator_Size8_Class5_Strong
             new['category'] = 'standard'
-            new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
+            if name[2] == 'overcharge':
+                new['name'] = standard_map[(name[1], name[2])]
+            else:
+                new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
 
         elif name[1] in internal_map:  # e.g. Int_CargoRack_Size8_Class1
             new['category'] = 'internal'
@@ -209,6 +221,9 @@ def lookup(module, ship_map, entitled=False) -> dict | None:  # noqa: C901, CCR0
         elif len(name) < 4 and name[1] == 'guardianfsdbooster':  # Hack! No class.
             (new['class'], new['rating']) = (str(name[2][4:]), 'H')
 
+        elif len(name) > 4 and name[1] == 'hyperdrive':  # e.g. Int_Hyperdrive_Overcharge_Size6_Class3
+            (new['class'], new['rating']) = (str(name[4][-1:]), 'C')
+
         else:
             if len(name) < 3:
                 raise AssertionError(f'{name}: length < 3]')

From 5cc08f25175e6286f6012cc0b3c3311ec685f37d Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sun, 14 Apr 2024 20:32:43 -0400
Subject: [PATCH 39/49] [Minor] Clarify Inheritence

---
 myNotebook.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index 88b4aec5..dd6e3429 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -63,7 +63,7 @@ class Label(tk.Label):
         super().__init__(master, **kw)
 
 
-class EntryMenu:
+class EntryMenu(ttk.Entry):
     """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
 
     def __init__(self, *args, **kwargs) -> None:
@@ -120,7 +120,7 @@ class EntryMenu:
             pass
 
 
-class Entry(ttk.Entry, EntryMenu):
+class Entry(EntryMenu):
     """Custom t(t)k.Entry class to fix some display issues."""
 
     # DEPRECATED: Migrate to ttk.Entry or EntryMenu. Will remove in 5.12 or later.

From 7a030cd7f4775493c17743ac4fb628ba29535a9d Mon Sep 17 00:00:00 2001
From: Phoebe <40956085+C1701D@users.noreply.github.com>
Date: Thu, 28 Mar 2024 16:02:23 +0000
Subject: [PATCH 40/49] Merge pull request #2182 from
 HullSeals/enhancement/2155/add-docking-schemas

[2155] Add DockingDenied and DockingGranted Schemas
LGTM
---
 plugins/eddn.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/plugins/eddn.py b/plugins/eddn.py
index 687b39b7..586242a5 100644
--- a/plugins/eddn.py
+++ b/plugins/eddn.py
@@ -1880,6 +1880,74 @@ class EDDN:
 
         return None
 
+    def export_journal_dockingdenied(
+            self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]
+    ) -> str | None:
+        """
+        Send a DockingDenied to EDDN on the correct schema.
+
+        :param cmdr: the commander under which this upload is made
+        :param is_beta: whether or not we are in beta mode
+        :param entry: the journal entry to send
+
+        Example:
+        {
+            "timestamp":"2022-06-10T10:09:41Z",
+            "event":"DockingDenied",
+            "Reason":"RestrictedAccess",
+            "MarketID":3706117376,
+            "StationName":"V7G-T1G",
+            "StationType":"FleetCarrier"
+        }
+        """
+        #######################################################################
+        # Elisions
+        #######################################################################
+        # In case Frontier ever add any
+        entry = filter_localised(entry)
+
+        msg = {
+            '$schemaRef': f'https://eddn.edcd.io/schemas/dockingdenied/1{"/test" if is_beta else ""}',
+            'message': entry
+        }
+
+        this.eddn.send_message(cmdr, msg)
+        return None
+
+    def export_journal_dockinggranted(
+            self, cmdr: str, is_beta: bool, entry: Mapping[str, Any]
+    ) -> str | None:
+        """
+        Send a DockingDenied to EDDN on the correct schema.
+
+        :param cmdr: the commander under which this upload is made
+        :param is_beta: whether or not we are in beta mode
+        :param entry: the journal entry to send
+
+        Example:
+        {
+            "timestamp":"2023-10-01T14:56:34Z",
+            "event":"DockingGranted",
+            "LandingPad":41,
+            "MarketID":3227312896,
+            "StationName":"Evans Horizons",
+            "StationType":"Coriolis"
+        }
+        """
+        #######################################################################
+        # Elisions
+        #######################################################################
+        # In case Frontier ever add any
+        entry = filter_localised(entry)
+
+        msg = {
+            '$schemaRef': f'https://eddn.edcd.io/schemas/dockinggranted/1{"/test" if is_beta else ""}',
+            'message': entry
+        }
+
+        this.eddn.send_message(cmdr, msg)
+        return None
+
     def canonicalise(self, item: str) -> str:
         """
         Canonicalise the given commodity name.
@@ -2327,6 +2395,12 @@ def journal_entry(  # noqa: C901, CCR001
         if event_name == 'fcmaterials':
             return this.eddn.export_journal_fcmaterials(cmdr, is_beta, entry)
 
+        if event_name == "dockingdenied":
+            return this.eddn.export_journal_dockingdenied(cmdr, is_beta, entry)
+
+        if event_name == "dockinggranted":
+            return this.eddn.export_journal_dockinggranted(cmdr, is_beta, entry)
+
         if event_name == 'approachsettlement':
             # An `ApproachSettlement` can appear *before* `Location` if you
             # logged at one.  We won't have necessary augmentation data

From 96be38d85b1836f5d0cbefe4873b080d0743daca Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Sat, 30 Mar 2024 16:02:54 -0400
Subject: [PATCH 41/49] [Minor] Add Missing Future Annotation

---
 config/linux.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/config/linux.py b/config/linux.py
index 73100800..51a40626 100644
--- a/config/linux.py
+++ b/config/linux.py
@@ -5,6 +5,8 @@ Copyright (c) EDCD, All Rights Reserved
 Licensed under the GNU General Public License.
 See LICENSE file.
 """
+from __future__ import annotations
+
 import os
 import pathlib
 import sys

From 5c49ea16b9d5f830ea6205c357062569c8bd01b5 Mon Sep 17 00:00:00 2001
From: David Sangrey <davidsangrey@gmail.com>
Date: Thu, 11 Apr 2024 09:52:57 -0400
Subject: [PATCH 42/49] Merge pull request #2197 from
 EDCD/submodule-change/8646630819

[Auto-generated] Submodule Updates 8646630819
---
 FDevIDs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/FDevIDs b/FDevIDs
index 7205c793..7cffab3d 160000
--- a/FDevIDs
+++ b/FDevIDs
@@ -1 +1 @@
-Subproject commit 7205c79331f42c1a28b757b27467f79ff106716b
+Subproject commit 7cffab3d913b788f981923687203399c22cf358f

From c4baf66dc36e692584856d7dc6a796ab3dffc924 Mon Sep 17 00:00:00 2001
From: David Sangrey <davidsangrey@gmail.com>
Date: Mon, 15 Apr 2024 16:33:34 -0400
Subject: [PATCH 43/49] Merge pull request #2204 from
 HullSeals/enhancement/2202/add-missing-modules

[2202] Add a number of missing modules
---
 edmc_data.py  | 31 ++++++++++++++++++++++++-------
 outfitting.py | 17 ++++++++++++++++-
 2 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/edmc_data.py b/edmc_data.py
index c628ae8d..b7ed4556 100644
--- a/edmc_data.py
+++ b/edmc_data.py
@@ -69,6 +69,8 @@ outfitting_weapon_map = {
     'advancedtorppylon':                 'Torpedo Pylon',
     'atdumbfiremissile':                 'AX Missile Rack',
     'atmulticannon':                     'AX Multi-Cannon',
+    ('atmulticannon', 'v2'):             'Enhanced AX Multi-Cannon',
+    ('atdumbfiremissile', 'v2'):         'Enhanced AX Missile Rack',
     'basicmissilerack':                  'Seeker Missile Rack',
     'beamlaser':                         'Beam Laser',
     ('beamlaser', 'heat'):               'Retributor Beam Laser',
@@ -88,6 +90,8 @@ outfitting_weapon_map = {
     'mining_abrblstr':                   'Abrasion Blaster',
     'mining_seismchrgwarhd':             'Seismic Charge Launcher',
     'mining_subsurfdispmisle':           'Sub-Surface Displacement Missile',
+    'human_extraction':                  'Sub-Surface Extraction Missile',
+    'atventdisruptorpylon':              'Guardian Nanite Torpedo Pylon',
     'mininglaser':                       'Mining Laser',
     ('mininglaser', 'advanced'):         'Mining Lance Beam Laser',
     'multicannon':                       'Multi-Cannon',
@@ -266,6 +270,11 @@ outfitting_weaponrating_map = {
     'hpt_slugshot_turret_medium':                'D',
     'hpt_slugshot_turret_large':                 'C',
     'hpt_xenoscannermk2_basic_tiny':             '?',
+    'hpt_atmulticannon_gimbal_large':            'C',
+    'hpt_atmulticannon_gimbal_medium':           'E',
+    'hpt_human_extraction_fixed_medium':         'B',
+    'hpt_atventdisruptorpylon_fixed_medium':     'I',
+    'hpt_atventdisruptorpylon_fixed_large':      'I',
 }
 
 # Old standard weapon variants
@@ -278,13 +287,14 @@ outfitting_weaponoldvariant_map = {
 }
 
 outfitting_countermeasure_map = {
-    'antiunknownshutdown':        ('Shutdown Field Neutraliser', 'F'),
-    'chafflauncher':              ('Chaff Launcher', 'I'),
-    'electroniccountermeasure':   ('Electronic Countermeasure', 'F'),
-    'heatsinklauncher':           ('Heat Sink Launcher', 'I'),
-    'plasmapointdefence':         ('Point Defence', 'I'),
-    'xenoscanner':                ('Xeno Scanner', 'E'),
-    'xenoscannermk2':             ('Unknown Xeno Scanner Mk II', '?'),
+    'antiunknownshutdown':          ('Shutdown Field Neutraliser', 'F'),
+    ('antiunknownshutdown', 'v2'):  ('Thargoid Pulse Neutraliser', 'E'),
+    'chafflauncher':                ('Chaff Launcher', 'I'),
+    'electroniccountermeasure':     ('Electronic Countermeasure', 'F'),
+    'heatsinklauncher':             ('Heat Sink Launcher', 'I'),
+    'plasmapointdefence':           ('Point Defence', 'I'),
+    'xenoscanner':                  ('Xeno Scanner', 'E'),
+    'xenoscannermk2':               ('Unknown Xeno Scanner Mk II', '?'),
 }
 
 outfitting_utility_map = {
@@ -347,6 +357,7 @@ outfitting_standard_map = {
     'guardianpowerdistributor':     'Guardian Hybrid Power Distributor',
     'guardianpowerplant':           'Guardian Hybrid Power Plant',
     'hyperdrive':                   'Frame Shift Drive',
+    ('hyperdrive', 'overcharge'):   'Frame Shift Drive',
     'lifesupport':                  'Life Support',
     # 'planetapproachsuite':        handled separately
     'powerdistributor':             'Power Distributor',
@@ -376,6 +387,11 @@ outfitting_internal_map = {
     'refinery':                     'Refinery',
     'recon':                        'Recon Limpet Controller',
     'repair':                       'Repair Limpet Controller',
+    'rescue':                       'Rescue Limpet Controller',
+    'mining':                       'Mining Multi Limpet Controller',
+    'xeno':                         'Xeno Multi Limpet Controller',
+    'operations':                   'Operations Multi Limpet Controller',
+    'universal':                    'Universal Multi Limpet Controller',
     'repairer':                     'Auto Field-Maintenance Unit',
     'resourcesiphon':               'Hatch Breaker Limpet Controller',
     'shieldcellbank':               'Shield Cell Bank',
@@ -383,6 +399,7 @@ outfitting_internal_map = {
     ('shieldgenerator', 'fast'):    'Bi-Weave Shield Generator',
     ('shieldgenerator', 'strong'):  'Prismatic Shield Generator',
     'unkvesselresearch':            'Research Limpet Controller',
+    'expmodulestabiliser':          'Experimental Weapon Stabiliser',
 }
 
 # Dashboard Flags constants
diff --git a/outfitting.py b/outfitting.py
index a5ccb4be..d290ad3b 100644
--- a/outfitting.py
+++ b/outfitting.py
@@ -100,6 +100,12 @@ def lookup(module, ship_map, entitled=False) -> dict | None:  # noqa: C901, CCR0
     elif not entitled and name[1] == 'planetapproachsuite':
         return None
 
+    # V2 Shutdown Field Neutralizer - Hpt_AntiUnknownShutdown_Tiny_V2
+    elif name[0] == 'hpt' and name[1] in countermeasure_map and len(name) == 4 and name[3] == 'v2':
+        new['category'] = 'utility'
+        new['name'], new['rating'] = countermeasure_map[name[1]]
+        new['class'] = weaponclass_map[name[-2]]
+
     # Countermeasures - e.g. Hpt_PlasmaPointDefence_Turret_Tiny
     elif name[0] == 'hpt' and name[1] in countermeasure_map:
         new['category'] = 'utility'
@@ -181,12 +187,18 @@ def lookup(module, ship_map, entitled=False) -> dict | None:  # noqa: C901, CCR0
         if name[1] == 'dronecontrol':  # e.g. Int_DroneControl_Collection_Size1_Class1
             name.pop(0)
 
+        elif name[1] == 'multidronecontrol':  # e.g. Int_MultiDroneControl_Rescue_Size3_Class3
+            name.pop(0)
+
         elif name[-1] == 'free':  # Starter Sidewinder or Freagle modules - just treat them like vanilla modules
             name.pop()
 
         if name[1] in standard_map:  # e.g. Int_Engine_Size2_Class1, Int_ShieldGenerator_Size8_Class5_Strong
             new['category'] = 'standard'
-            new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
+            if name[2] == 'overcharge':
+                new['name'] = standard_map[(name[1], name[2])]
+            else:
+                new['name'] = standard_map[len(name) > 4 and (name[1], name[4]) or name[1]]
 
         elif name[1] in internal_map:  # e.g. Int_CargoRack_Size8_Class1
             new['category'] = 'internal'
@@ -211,6 +223,9 @@ def lookup(module, ship_map, entitled=False) -> dict | None:  # noqa: C901, CCR0
         elif len(name) < 4 and name[1] == 'guardianfsdbooster':  # Hack! No class.
             (new['class'], new['rating']) = (str(name[2][4:]), 'H')
 
+        elif len(name) > 4 and name[1] == 'hyperdrive':  # e.g. Int_Hyperdrive_Overcharge_Size6_Class3
+            (new['class'], new['rating']) = (str(name[4][-1:]), 'C')
+
         else:
             if len(name) < 3:
                 raise AssertionError(f'{name}: length < 3]')

From 3cbe517fd6077e0e37141b10a877ff39a2724251 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Mon, 15 Apr 2024 16:44:18 -0400
Subject: [PATCH 44/49] [Translations] Update Translations and Requirements

---
 L10n/cs.strings         |   6 +-
 L10n/de.strings         |  20 +-
 L10n/es.strings         |   6 +-
 L10n/it.strings         |  20 +-
 L10n/ja.strings         |   6 +-
 L10n/ko.strings         |   6 +-
 L10n/pl.strings         |   6 +-
 L10n/pt-BR.strings      |   6 +-
 L10n/pt-PT.strings      |   6 +-
 L10n/ru.strings         |  35 +-
 L10n/sr-Latn-BA.strings |   6 +-
 L10n/sr-Latn.strings    |  20 +-
 L10n/tr.strings         | 805 ++++++++++++++++++++++++++++++++++++++++
 L10n/zh-Hans.strings    |   6 +-
 requirements.txt        |   4 +-
 15 files changed, 917 insertions(+), 41 deletions(-)
 create mode 100644 L10n/tr.strings

diff --git a/L10n/cs.strings b/L10n/cs.strings
index ba11dc39..071b5e69 100644
--- a/L10n/cs.strings
+++ b/L10n/cs.strings
@@ -178,13 +178,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Výchozí";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/de.strings b/L10n/de.strings
index 2b7c72c4..be36b877 100644
--- a/L10n/de.strings
+++ b/L10n/de.strings
@@ -231,6 +231,18 @@
 /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
 "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Eins oder mehr deiner aktivierten Plugins konnte nicht geladen werden. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Dies könnte an einer falschen Ordnerstruktur liegen. Die Datei load.py sollte sich unter plugins/PLUGIN_NAME/load.py befinden.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst.";
 
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Einer oder mehrere deiner URL-Anbieter waren ungültig und wurden zurückgesetzt:\n\n";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} war zuvor auf {OLDPROV} festgelegt und wurde zu {NEWPROV} zurückgesetzt.\n";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Standardanbieter wurden zurückgesetzt";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Warte auf vollständige CMDR Anmeldung";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "Journal-Ordner bereits gesperrt";
 
@@ -246,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Standard";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -768,3 +780,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Schiffe";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} ist verfügbar";
diff --git a/L10n/es.strings b/L10n/es.strings
index d2cb7b4b..18874ccf 100644
--- a/L10n/es.strings
+++ b/L10n/es.strings
@@ -184,13 +184,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Por defecto";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/it.strings b/L10n/it.strings
index e5f6ece6..c1cd9b33 100644
--- a/L10n/it.strings
+++ b/L10n/it.strings
@@ -231,6 +231,18 @@
 /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
 "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Impossibile caricare uno o più plugin abilitati. Consulta l'elenco nella scheda '{PLUGINS}' di '{FILE}' > '{SETTINGS}'. Ciò potrebbe essere causato da un'errata struttura delle cartelle. Il file load.py dovrebbe trovarsi in plugins/PLUGIN_NAME/load.py.\n\nPuoi disabilitare un plugin rinominando la sua cartella in modo che abbia '{DISABLED}' alla fine del nome.";
 
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Uno o più Provider URL non erano validi e sono stati ripristinati:\n";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} era impostato su {OLDPROV} ed è stato reimpostato su {NEWPROV}\n";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Reset dei provider predefiniti";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "In attesa del login completo al CMDR";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "cartella Journal già bloccata";
 
@@ -246,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Predefinito";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normale";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -789,3 +801,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Navi";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} è disponibile";
diff --git a/L10n/ja.strings b/L10n/ja.strings
index 68a9fd34..82d9f8df 100644
--- a/L10n/ja.strings
+++ b/L10n/ja.strings
@@ -246,13 +246,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "デフォルト";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "自動";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "通常版";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "ベータ版";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/ko.strings b/L10n/ko.strings
index 577695b0..e29ad74f 100644
--- a/L10n/ko.strings
+++ b/L10n/ko.strings
@@ -202,13 +202,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "기본값";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "자동";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "일반";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "베타";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/pl.strings b/L10n/pl.strings
index 72a66718..547406b1 100644
--- a/L10n/pl.strings
+++ b/L10n/pl.strings
@@ -234,13 +234,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Domyślne";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normalny";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings
index e719101a..605e0559 100644
--- a/L10n/pt-BR.strings
+++ b/L10n/pt-BR.strings
@@ -234,13 +234,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Padrão";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Automático";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings
index ea292b14..0fd5b585 100644
--- a/L10n/pt-PT.strings
+++ b/L10n/pt-PT.strings
@@ -246,13 +246,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Predefinido";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/ru.strings b/L10n/ru.strings
index c3c0fae1..19449e21 100644
--- a/L10n/ru.strings
+++ b/L10n/ru.strings
@@ -213,12 +213,36 @@
 /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
 "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{DISABLED}'.";
 
+/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
+"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID файлы не найдены! Некоторые функции, связанные с товарами, могут быть отключены.\n\nВы хотите открыть страницу Wiki о том, как настроить вспомогательные модули?";
+
+/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
+"FDevIDs: Missing Commodity Files" = "FDevIDs: отсутствуют файлы товаров";
+
 /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
 "Plugins" = "Плагины";
 
 /* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
 "EDMC: Plugins Without Python 3.x Support" = "EDMC: Без поддержки Python 3.x";
 
+/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
+"EDMC: Broken Plugins" = "EDMC: Неработающие плагины";
+
+/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
+"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Не удалось загрузить один или несколько включенных плагинов. Пожалуйста, посмотрите список на вкладке '{PLUGINS}' в разделе '{FILE}' > '{SETTINGS}'. Причиной может быть неправильная структура папок. Файл load.py должен находиться в папке plugins/PLUGIN_NAME/load.py.\n\nВы можете отключить плагин, переименовав его папку так, чтобы в конце названия стояло '{DISABLED}'.";
+
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Один или несколько ваших URL-провайдеров оказались некорректными и были сброшены:";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} был установлен на {OLDPROV} и был сброшен на {NEWPROV}";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: сброс провайдеров по умолчанию";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Ожидание полного входа в систему";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "Каталог журнала уже заблокирован";
 
@@ -234,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "По умолчанию";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Автоматический";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Стандартный";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Бета";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -471,6 +495,9 @@
 /* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
 "Information on migrating plugins" = "Информация о мигрирующих плагинах";
 
+/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
+"Broken Plugins" = "Неработающие плагины";
+
 /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
 "Disabled Plugins" = "Отключенные плагины";
 
@@ -774,3 +801,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Корабли";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} доступен";
diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings
index 2950f8b8..f541416a 100644
--- a/L10n/sr-Latn-BA.strings
+++ b/L10n/sr-Latn-BA.strings
@@ -246,13 +246,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Standardna";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Automatski";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normalni";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings
index 83da541a..e801d067 100644
--- a/L10n/sr-Latn.strings
+++ b/L10n/sr-Latn.strings
@@ -231,6 +231,18 @@
 /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
 "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više vaših aktivnih pluginova se nije učitao. Molimo pogledajte listu na '{PLUGINS}' tabu u okviru '{FILE}' > '{SETTINGS}'. Obo vi moglo da se desi zbog pogrešne strukture foldera. load.py fajl bi trebao da se nalazi unutar plugins/PLUGIN_NAME/load.py.\n\nPojedinačni plugin možete deaktivirati promenom imena njegovog foldera dodavanjem '{DISABLED}' na kraju.";
 
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Jedan ili više vaših URL Provajdera su pogrešni, pa su resetovani:\n";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} je bio postavljen na {OLDPROV} i sada je resetovan na {NEWPROV}\n";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Podrazumevani Provajderi resetovani";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Čekam da se CMDR potpuno uloguje";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "Žurnal direktorijum je već zaključan";
 
@@ -246,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Podrazumevano";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -789,3 +801,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Brodovi";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} je dostupna";
diff --git a/L10n/tr.strings b/L10n/tr.strings
new file mode 100644
index 00000000..9d37dfa1
--- /dev/null
+++ b/L10n/tr.strings
@@ -0,0 +1,805 @@
+/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
+"Send flight log and CMDR status to EDSM" = "Uçuş günlüğünü ve CMDR durumunu EDSM'e gönder";
+
+/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
+"Open Log Folder" = "Günlük Klasörünü Aç";
+
+/* inara.py:Text Inara Show API key; In files: inara.py:305; */
+"Show API Key" = "API Anahtarını Göster";
+/* Language name */
+"!Language" = "Türkçe";
+
+/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */
+"Error: Frontier CAPI didn't respond" = "Hata: Frontier CAPI yanıt vermedi";
+
+/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */
+"Error: Frontier server is lagging" = "Hata: Frontier sunucusu gecikmesi";
+
+/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */
+"Docked but unknown station: EDO Settlement?" = "Kenetlendi ancak bilinmeyen istasyon: EDO yerleşimi?";
+
+/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */
+"Error: Invalid Credentials" = "Hata: Geçersiz Kimlik";
+
+/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
+"Error: Wrong Cmdr" = "Hata: Yanlış CMDR";
+
+/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
+"Error" = "Hata";
+
+/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */
+"Error: Couldn't check token customer_id" = "Hata: token kontrol edilemedi customer_id";
+
+/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */
+"Error: customer_id doesn't match!" = "Hata: customer_id uyuşmuyor!";
+
+/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
+"Error: unable to get token" = "Hata: token erişilemedi";
+
+/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
+"Frontier CAPI down for maintenance" = "Frontier CAPI bakım için kapalı durumda";
+
+/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
+"Frontier CAPI query failure" = "Frontier CAPI sorgulama hatası";
+
+/* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */
+"Update" = "Güncelle";
+
+/* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */
+"Always on top" = "Her zaman üstte";
+
+/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
+"Unknown" = "Bilinmeyen";
+
+/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
+"Error: Check E:D journal file location" = "Hata: E:D günlük dosyası konumunu kontrol edin";
+
+/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */
+"Cmdr" = "Cmdr";
+
+/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:915; EDMarketConnector.py:1487; */
+"Role" = "Rol";
+
+/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:915; EDMarketConnector.py:1497; EDMarketConnector.py:1520; stats.py:405; */
+"Ship" = "Gemi";
+
+/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:916; */
+"Suit" = "Süit";
+
+/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:917; prefs.py:614; stats.py:407; */
+"System" = "Sistem";
+
+/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:918; prefs.py:632; prefs.py:770; stats.py:408; */
+"Station" = "İstasyon";
+
+/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:921; EDMarketConnector.py:939; EDMarketConnector.py:942; EDMarketConnector.py:2264; */
+"File" = "Dosya";
+
+/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
+"Edit" = "Düzenle";
+
+/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
+"View" = "Görüntüle";
+
+/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
+"Window" = "Pencere";
+
+/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
+"Help" = "Yardım";
+
+/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:928; EDMarketConnector.py:959; EDMarketConnector.py:1804; */
+"About {APP}" = "Hakkında {APP}";
+
+/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
+"Check for Updates..." = "Güncellemeleri Denetle...";
+
+/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
+"Save Raw Data..." = "Ham Verileri Kaydet...";
+
+/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:932; EDMarketConnector.py:947; stats.py:402; */
+"Status" = "Durum";
+
+/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
+"Documentation" = "Dokümantasyon";
+
+/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
+"Troubleshooting" = "Sorun giderme";
+
+/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
+"Report A Bug" = "Hata Bildir";
+
+/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
+"Privacy Policy" = "Gizlilik Politikası";
+
+/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:937; EDMarketConnector.py:957; EDMarketConnector.py:1838; */
+"Release Notes" = "Sürüm notları";
+
+/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:949; EDMarketConnector.py:2265; prefs.py:241; */
+"Settings" = "Ayarlar";
+
+/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:950; */
+"Exit" = "Çıkış";
+
+/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
+"Copy" = "Kopyala";
+
+/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
+"CAPI auth disabled by killswitch" = "CAPI kimlik doğrulaması killswitch tarafından devre dışı bırakıldı";
+
+/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */
+"Logging in..." = "Giriş yapılıyor...";
+
+/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:994; EDMarketConnector.py:1657; */
+"Authentication successful" = "Kimlik doğrulama başarılı";
+
+/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:1025; */
+"You're not docked at a station!" = "Bir istasyona kenetli değilsiniz!";
+
+/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:1033; */
+"Station doesn't have anything!" = "İstasyon içeriği mevcut değil.";
+
+/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */
+"Station doesn't have a market!" = "İstasyon'da market bulunmuyor!";
+
+/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */
+"CAPI query aborted: Cmdr name unknown" = "CAPI sorgusu iptal edildi: Cmdr adı bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */
+"CAPI query aborted: Game mode unknown" = "CAPI sorgusu iptal edildi: Oyun modu bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */
+"CAPI query aborted: GameVersion unknown" = "CAPI sorgusu iptal edildi: GameVersion bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */
+"CAPI query aborted: Current system unknown" = "CAPI sorgusu iptal edildi: Mevcut sistem bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */
+"CAPI query aborted: In other-ship multi-crew" = "CAPI sorgusu iptal edildi: Başka gemi çoklu-mürettebatı";
+
+/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */
+"CAPI query aborted: CQC (Arena) detected" = "CAPI sorgusu iptal edildi: CQC (Arena) tespit edildi";
+
+/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */
+"Fetching data..." = "Veri işleniyor...";
+
+/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
+"CAPI fleetcarrier disabled by killswitch" = "CAPI filo taşıyıcısı killswitch tarafından devre dışı bırakıldı";
+
+/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
+"CAPI: No fleetcarrier data returned" = "CAPI: Filo taşıyıcı verisi gelmedi.";
+
+/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
+"CAPI: Fleetcarrier data incomplete" = "CAPI: Filo taşıyıcı verileri eksik";
+
+/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
+"CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi";
+
+/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1246; stats.py:333; */
+"Who are you?!" = "Sen kimsin?!";
+
+/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1252; stats.py:341; */
+"Where are you?!" = "Neredesin?!";
+
+/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */
+"What are you flying?!" = "Ne uçuruyorsun?!";
+
+/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */
+"Frontier CAPI server error" = "Frontier CAPI sunucu hatası";
+
+/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */
+"CAPI: Refreshing access token..." = "CAPI: Erişim token yenileniyor...";
+
+/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */
+"Last updated at %H:%M:%S" = "Son güncelleme %H:%M:%S";
+
+/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1462; */
+"Fighter" = "Avcı";
+
+/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1463; */
+"Gunner" = "Nişancı";
+
+/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1464; */
+"Helm" = "Dümen";
+
+/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1742; */
+"cooldown {SS}s" = "bekleme süresi {SS}s";
+
+/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1864; prefs.py:292; */
+"OK" = "Tamam";
+
+/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1936; */
+"Shutting down..." = "Kapanıyor...";
+
+/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
+"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası henüz Python 3.x desteğine sahip değil.  '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Güncellenmiş bir sürümün mevcut olup olmadığını kontrol edin, aksi takdirde geliştiriciyi Python 3.x kodunu güncellemesi gerektiği konusunda uyarmalısınız.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz.";
+
+/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
+"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID Dosyaları bulunamadı! Ürünlerle ilgili bazı işlevler devre dışı bırakılabilir.\n\nAlt modüllerin nasıl kurulacağına ilişkin Wiki sayfasını açmak ister misiniz?";
+
+/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
+"FDevIDs: Missing Commodity Files" = "FDevIDs: Eksik Ürün Dosyaları";
+
+/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
+"Plugins" = "Eklentiler";
+
+/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
+"EDMC: Plugins Without Python 3.x Support" = "EDMC: Python 3.x Desteği Olmayan Eklentiler";
+
+/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
+"EDMC: Broken Plugins" = "EDMC: Bozuk Eklentiler";
+
+/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
+"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası yüklenemedi. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Sorunun sebebi yanlış klasör yapısı olabilir. Load.py dosyası, plugins/PLUGIN_NAME/load.py altında bulunmalıdır.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz.";
+
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "URL Sağlayıcılarınızdan bir veya daha fazlası geçersizdi ve sıfırlandı:";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} , {OLDPROV}'a ayarlanmıştı ancak {NEWPROV}'a sıfırlandı";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Varsayılan Sağlayıcıların Sıfırlanması";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Tam CMDR Girişi Bekleniyor";
+
+/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
+"Journal directory already locked" = "Günlük dizini zaten kilitli";
+
+/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:225:226; */
+"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Yeni Günlük Dizini konumu zaten kilitli.{CR}Çözüm için Tekrar deneyebilir veya Yoksay'ı seçebilirsiniz.";
+
+/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:230; */
+"Retry" = "Tekrar Dene";
+
+/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:234; */
+"Ignore" = "Yoksay";
+
+/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
+"Default" = "Varsayılan";
+
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
+"Auto" = "Otomatik";
+
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
+"Normal" = "Normal";
+
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
+"Beta" = "Beta";
+
+/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
+"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Coriolis.io gemi ekipman tasarımları ile kullanılacak URL'yi ayarlayın. Link'in '/import?data=' ile bitmesi GEREKTİĞİNİ unutmayın.";
+
+/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */
+"Normal URL" = "Normal URL";
+
+/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */
+"Reset" = "Sıfırla";
+
+/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */
+"Beta URL" = "Beta URL";
+
+/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:116; */
+"Override Beta/Normal Selection" = "Beta/Normal Seçimi Geçersiz Kıl";
+
+/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:156; */
+"Invalid Coriolis override mode!" = "Geçersiz Coriolis geçersiz kılma modu!";
+
+/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */
+"Error: Can't connect to EDDN" = "Hata: EDDN'e bağlanılamıyor";
+
+/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:576; */
+"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Hatası: EDMC sürümü EDDN için çok eski. Lütfen güncelle.";
+
+/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:582; */
+"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Hatası: Doğrulama Başarısız Oldu (EDMC sürümü eski olabilir?). Günlüğe bakın";
+
+/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:587; */
+"EDDN Error: Returned {STATUS} status code" = "EDDN Hatası: {STATUS} durum kodu ile döndü.";
+
+/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:2041; */
+"Send station data to the Elite Dangerous Data Network" = "İstasyon verilerini Elite Dangerous Data Network'e gönderin";
+
+/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:2052; */
+"Send system and scan data to the Elite Dangerous Data Network" = "Sistem ve tarama verilerini Elite Dangerous Data Network'e gönderin";
+
+/* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:2063; */
+"Delay sending until docked" = "Kenetlenmeye kadar gönderimi ertele";
+
+/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:2178; */
+"EDDN journal handler disabled. See Log." = "EDDN günlük düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
+
+/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:2507; */
+"Sending data to EDDN..." = "Veriler EDDN'e gönderiliyor...";
+
+/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:319; */
+"Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map kimlik bilgileri";
+
+/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:341; */
+"Commander Name" = "Cmdr ismi";
+
+/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:350; inara.py:278; */
+"API Key" = "API Anahtarı";
+
+/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:394; prefs.py:527; prefs.py:1157; prefs.py:1190; stats.py:154; stats.py:173; stats.py:192; stats.py:209; */
+"None" = "Hiçbiri";
+
+/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:516; */
+"EDSM Handler disabled. See Log." = "EDSM düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
+
+/* edsm.py: EDSM - Only Live data; In files: edsm.py:632; */
+"EDSM only accepts Live galaxy data" = "EDSM sadece canlı galaxy verilerini kabul eder";
+
+/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:916; edsm.py:1048; */
+"Error: EDSM {MSG}" = "Hata: EDSM {MSG}";
+
+/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */
+"Error: Can't connect to EDSM" = "Hata: EDSM'e bağlanılamıyor";
+
+/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:257; */
+"Send flight log and Cmdr status to Inara" = "Uçuş günlüğünü ve Cmdr durumunu Inara'ya gönder";
+
+/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */
+"Inara credentials" = "Inara kimlik bilgileri";
+
+/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; inara.py: Inara - Only Live data; In files: inara.py:384; inara.py:386; */
+"Inara only accepts Live galaxy data" = "Inara sadece canlı galaxy verilerini kabul eder";
+
+/* inara.py: INARA support disabled via killswitch; In files: inara.py:395; */
+"Inara disabled. See Log." = "Inara devre dışı. Kayıt geçmişini kontrol edin.";
+
+/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
+"Error: Inara {MSG}" = "Hata: Inara {MSG}";
+
+/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
+"Preferences" = "Tercihler";
+
+/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
+"Please choose what data to save" = "Lütfen hangi verilerin kaydedileceğini seçin";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:341; */
+"Market data in CSV format file" = "CSV formatında Market verileri";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:350; */
+"Market data in Trade Dangerous format file" = "Trade Dangerous formatında Market verileri";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:360; */
+"Ship loadout" = "Gemi Ekipmanları";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:370; */
+"Automatically update on docking" = "Kenetlenme sırasında otomatik güncelle";
+
+/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
+"File location" = "Dosya konumu";
+
+/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
+"Change..." = "Değiştir...";
+
+/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
+"Browse..." = "Gezin...";
+
+/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:405; */
+"Output" = "Kayıt";
+
+/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
+"E:D journal file location" = "E:D günlük dosya konumu";
+
+/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
+"CAPI Settings" = "CAPI ayarları";
+
+/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
+"Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir";
+
+/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
+"Keyboard shortcut" = "Klavye Kısayolu";
+
+/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
+"Hotkey" = "Kısayoltuşu";
+
+/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
+"Re-start {APP} to use shortcuts" = "Kısayolları kullanmak için {APP}'i yeniden başlat";
+
+/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
+"{APP} needs permission to use shortcuts" = "{APP}'nin kısayolları kullanabilmesi için izne ihtiyacı var";
+
+/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
+"Open System Preferences" = "Sistem Tercihlerini Aç";
+
+/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
+"Only when Elite: Dangerous is the active app" = "Sadece Elite: Dangerous aktif uygulama olduğunda";
+
+/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:549; */
+"Play sound" = "Ses çal";
+
+/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:564; */
+"Disable Automatic Application Updates Check when in-game" = "Otomatik Uygulama Güncellemelerini Oyun sırasında Devre Dışı Bırak";
+
+/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:577; */
+"Preferred websites" = "Tercih edilen web siteleri";
+
+/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:588; */
+"Shipyard" = "Tersane";
+
+/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:600; */
+"Use alternate URL method" = "Alternatif URL yöntemi kullan";
+
+/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:653; */
+"Log Level" = "Günlük Seviyesi";
+
+/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:681; */
+"Configuration" = "Yapılandırma";
+
+/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:690; */
+"Main UI privacy options" = "Ana Arayüz gizlilik seçenekleri";
+
+/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:695; */
+"Hide private group name in UI" = "Özel grup ismini arayüzde gizle";
+
+/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:699; */
+"Hide multi-crew captain name" = "Çoklu-mürettebat ismini gizle";
+
+/* prefs.py: Preferences privacy tab title; In files: prefs.py:703; */
+"Privacy" = "Gizlilik";
+
+/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:716; */
+"Normal text" = "Normal metin";
+
+/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:718; */
+"Highlighted text" = "Vurgulanan metin";
+
+/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:727; */
+"Language" = "Dil";
+
+/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:737; */
+"Theme" = "Tema";
+
+/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:749; */
+"Dark" = "Karanlık";
+
+/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:756; */
+"Transparent" = "Şeffaflık";
+
+/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:802; */
+"UI Scale Percentage" = "Arayüz ölçek yüzdesi";
+
+/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:823; */
+"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 Varsayılan değerdir. Değişikliklerin geçerli olması için {CR}Yeniden Başlatma Gereklidir{CR}!";
+
+/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:833; */
+"Main window transparency" = "Ana pencere şeffaflığı";
+
+/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:853:856; */
+"100 means fully opaque.{CR}Window is updated in real time" = "100 tamamen opak anlamına gelir.{CR}Pencere gerçek zamanlı olarak güncellenir";
+
+/* prefs.py: Appearance option for Windows "minimize to system tray"; In files: prefs.py:885; */
+"Minimize to system tray" = "Simge durumuna küçült";
+
+/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:893; */
+"Appearance" = "Görünüm";
+
+/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
+"Plugins folder" = "Eklentiler klasörü";
+
+/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
+"Open" = "Aç";
+
+/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
+"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "İpucu: Bir eklentiyi, klasör adına{CR}'{EXT}' ekleyerek devre dışı bırakabilirsiniz";
+
+/* prefs.py: Label on list of enabled plugins; In files: prefs.py:934; */
+"Enabled Plugins" = "Etkin Eklentiler";
+
+/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:954; */
+"Plugins Without Python 3.x Support" = "Python 3.x Desteği Olmayan Eklentiler";
+
+/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
+"Information on migrating plugins" = "Eklentilerin taşınmasıyla ilgili bilgiler";
+
+/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
+"Broken Plugins" = "Bozuk Eklentiler";
+
+/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
+"Disabled Plugins" = "Devre Dışı Eklentiler";
+
+/* stats.py: Cmdr stats; In files: stats.py:58; */
+"Balance" = "Bakiye";
+
+/* stats.py: Cmdr stats; In files: stats.py:59; */
+"Loan" = "Borç";
+
+/* stats.py: Top rank; In files: stats.py:63; */
+"Elite" = "Elite";
+
+/* stats.py: Top rank +1; In files: stats.py:64; */
+"Elite I" = "Elite I";
+
+/* stats.py: Top rank +2; In files: stats.py:65; */
+"Elite II" = "Elite II";
+
+/* stats.py: Top rank +3; In files: stats.py:66; */
+"Elite III" = "Elite III";
+
+/* stats.py: Top rank +4; In files: stats.py:67; */
+"Elite IV" = "Elite IV";
+
+/* stats.py: Top rank +5; In files: stats.py:68; */
+"Elite V" = "Elite V";
+
+/* stats.py: Ranking; In files: stats.py:74; */
+"Combat" = "Combat";
+
+/* stats.py: Ranking; In files: stats.py:75; */
+"Trade" = "Trade";
+
+/* stats.py: Ranking; In files: stats.py:76; */
+"Explorer" = "Explorer";
+
+/* stats.py: Ranking; In files: stats.py:77; */
+"Mercenary" = "Mercenary";
+
+/* stats.py: Ranking; In files: stats.py:78; */
+"Exobiologist" = "Exobiologist";
+
+/* stats.py: Ranking; In files: stats.py:79; */
+"CQC" = "CQC";
+
+/* stats.py: Ranking; In files: stats.py:80; */
+"Federation" = "Federation";
+
+/* stats.py: Ranking; In files: stats.py:81; */
+"Empire" = "Empire";
+
+/* stats.py: Ranking; In files: stats.py:82; */
+"Powerplay" = "Powerplay";
+
+/* stats.py: Combat rank; In files: stats.py:91; */
+"Harmless" = "Harmless";
+
+/* stats.py: Combat rank; In files: stats.py:92; */
+"Mostly Harmless" = "Mostly Harmless";
+
+/* stats.py: Combat rank; In files: stats.py:93; */
+"Novice" = "Novice";
+
+/* stats.py: Combat rank; In files: stats.py:94; */
+"Competent" = "Competent";
+
+/* stats.py: Combat rank; In files: stats.py:95; */
+"Expert" = "Expert";
+
+/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:96; stats.py:176; */
+"Master" = "Master";
+
+/* stats.py: Combat rank; In files: stats.py:97; */
+"Dangerous" = "Dangerous";
+
+/* stats.py: Combat rank; In files: stats.py:98; */
+"Deadly" = "Deadly";
+
+/* stats.py: Trade rank; In files: stats.py:101; */
+"Penniless" = "Penniless";
+
+/* stats.py: Trade rank; In files: stats.py:102; */
+"Mostly Penniless" = "Mostly Penniless";
+
+/* stats.py: Trade rank; In files: stats.py:103; */
+"Peddler" = "Peddler";
+
+/* stats.py: Trade rank; In files: stats.py:104; */
+"Dealer" = "Dealer";
+
+/* stats.py: Trade rank; In files: stats.py:105; */
+"Merchant" = "Merchant";
+
+/* stats.py: Trade rank; In files: stats.py:106; */
+"Broker" = "Broker";
+
+/* stats.py: Trade rank; In files: stats.py:107; */
+"Entrepreneur" = "Entrepreneur";
+
+/* stats.py: Trade rank; In files: stats.py:108; */
+"Tycoon" = "Tycoon";
+
+/* stats.py: Explorer rank; In files: stats.py:111; */
+"Aimless" = "Aimless";
+
+/* stats.py: Explorer rank; In files: stats.py:112; */
+"Mostly Aimless" = "Mostly Aimless";
+
+/* stats.py: Explorer rank; In files: stats.py:113; */
+"Scout" = "Scout";
+
+/* stats.py: Explorer rank; In files: stats.py:114; */
+"Surveyor" = "Surveyor";
+
+/* stats.py: Explorer rank; In files: stats.py:115; */
+"Trailblazer" = "Trailblazer";
+
+/* stats.py: Explorer rank; In files: stats.py:116; */
+"Pathfinder" = "Pathfinder";
+
+/* stats.py: Explorer rank; In files: stats.py:117; */
+"Ranger" = "Ranger";
+
+/* stats.py: Explorer rank; In files: stats.py:118; */
+"Pioneer" = "Pioneer";
+
+/* stats.py: Mercenary rank; In files: stats.py:122; */
+"Defenceless" = "Defenceless";
+
+/* stats.py: Mercenary rank; In files: stats.py:123; */
+"Mostly Defenceless" = "Mostly Defenceless";
+
+/* stats.py: Mercenary rank; In files: stats.py:124; */
+"Rookie" = "Rookie";
+
+/* stats.py: Mercenary rank; In files: stats.py:125; */
+"Soldier" = "Soldier";
+
+/* stats.py: Mercenary rank; In files: stats.py:126; stats.py:128; */
+"Gunslinger" = "Gunslinger";
+
+/* stats.py: Mercenary rank; In files: stats.py:127; */
+"Warrior" = "Warrior";
+
+/* stats.py: Mercenary rank; In files: stats.py:129; */
+"Deadeye" = "Deadeye";
+
+/* stats.py: Exobiologist rank; In files: stats.py:132; */
+"Directionless" = "Directionless";
+
+/* stats.py: Exobiologist rank; In files: stats.py:133; */
+"Mostly Directionless" = "Mostly Directionless";
+
+/* stats.py: Exobiologist rank; In files: stats.py:134; */
+"Compiler" = "Compiler";
+
+/* stats.py: Exobiologist rank; In files: stats.py:135; */
+"Collector" = "Collector";
+
+/* stats.py: Exobiologist rank; In files: stats.py:136; */
+"Cataloguer" = "Cataloguer";
+
+/* stats.py: Exobiologist rank; In files: stats.py:137; */
+"Taxonomist" = "Taxonomist";
+
+/* stats.py: Exobiologist rank; In files: stats.py:138; */
+"Ecologist" = "Ecologist";
+
+/* stats.py: Exobiologist rank; In files: stats.py:139; */
+"Geneticist" = "Geneticist";
+
+/* stats.py: CQC rank; In files: stats.py:142; */
+"Helpless" = "Helpless";
+
+/* stats.py: CQC rank; In files: stats.py:143; */
+"Mostly Helpless" = "Mostly Helpless";
+
+/* stats.py: CQC rank; In files: stats.py:144; */
+"Amateur" = "Amateur";
+
+/* stats.py: CQC rank; In files: stats.py:145; */
+"Semi Professional" = "Semi Professional";
+
+/* stats.py: CQC rank; In files: stats.py:146; */
+"Professional" = "Professional";
+
+/* stats.py: CQC rank; In files: stats.py:147; */
+"Champion" = "Champion";
+
+/* stats.py: CQC rank; In files: stats.py:148; */
+"Hero" = "Hero";
+
+/* stats.py: CQC rank; In files: stats.py:149; */
+"Gladiator" = "Gladiator";
+
+/* stats.py: Federation rank; In files: stats.py:155; */
+"Recruit" = "Recruit";
+
+/* stats.py: Federation rank; In files: stats.py:156; */
+"Cadet" = "Cadet";
+
+/* stats.py: Federation rank; In files: stats.py:157; */
+"Midshipman" = "Midshipman";
+
+/* stats.py: Federation rank; In files: stats.py:158; */
+"Petty Officer" = "Petty Officer";
+
+/* stats.py: Federation rank; In files: stats.py:159; */
+"Chief Petty Officer" = "Chief Petty Officer";
+
+/* stats.py: Federation rank; In files: stats.py:160; */
+"Warrant Officer" = "Warrant Officer";
+
+/* stats.py: Federation rank; In files: stats.py:161; */
+"Ensign" = "Ensign";
+
+/* stats.py: Federation rank; In files: stats.py:162; */
+"Lieutenant" = "Lieutenant";
+
+/* stats.py: Federation rank; In files: stats.py:163; */
+"Lieutenant Commander" = "Lieutenant Commander";
+
+/* stats.py: Federation rank; In files: stats.py:164; */
+"Post Commander" = "Post Commander";
+
+/* stats.py: Federation rank; In files: stats.py:165; */
+"Post Captain" = "Post Captain";
+
+/* stats.py: Federation rank; In files: stats.py:166; */
+"Rear Admiral" = "Rear Admiral";
+
+/* stats.py: Federation rank; In files: stats.py:167; */
+"Vice Admiral" = "Vice Admiral";
+
+/* stats.py: Federation rank; In files: stats.py:168; */
+"Admiral" = "Admiral";
+
+/* stats.py: Empire rank; In files: stats.py:174; */
+"Outsider" = "Outsider";
+
+/* stats.py: Empire rank; In files: stats.py:175; */
+"Serf" = "Serf";
+
+/* stats.py: Empire rank; In files: stats.py:177; */
+"Squire" = "Squire";
+
+/* stats.py: Empire rank; In files: stats.py:178; */
+"Knight" = "Knight";
+
+/* stats.py: Empire rank; In files: stats.py:179; */
+"Lord" = "Lord";
+
+/* stats.py: Empire rank; In files: stats.py:180; */
+"Baron" = "Baron";
+
+/* stats.py: Empire rank; In files: stats.py:181; */
+"Viscount" = "Viscount";
+
+/* stats.py: Empire rank; In files: stats.py:182; */
+"Count" = "Count";
+
+/* stats.py: Empire rank; In files: stats.py:183; */
+"Earl" = "Earl";
+
+/* stats.py: Empire rank; In files: stats.py:184; */
+"Marquis" = "Marquis";
+
+/* stats.py: Empire rank; In files: stats.py:185; */
+"Duke" = "Duke";
+
+/* stats.py: Empire rank; In files: stats.py:186; */
+"Prince" = "Prince";
+
+/* stats.py: Empire rank; In files: stats.py:187; */
+"King" = "King";
+
+/* stats.py: Power rank; In files: stats.py:193; */
+"Rating 1" = "Reyting 1";
+
+/* stats.py: Power rank; In files: stats.py:194; */
+"Rating 2" = "Reyting 2";
+
+/* stats.py: Power rank; In files: stats.py:195; */
+"Rating 3" = "Reyting 3";
+
+/* stats.py: Power rank; In files: stats.py:196; */
+"Rating 4" = "Reyting 4";
+
+/* stats.py: Power rank; In files: stats.py:197; */
+"Rating 5" = "Reyting 5";
+
+/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:315; */
+"Status: Don't yet know your Commander name" = "Durum: Cmdr adınızı henüz bilmiyorum";
+
+/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:323; */
+"Status: No CAPI data yet" = "Durum: Henüz CAPI verisi yok";
+
+/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:409; */
+"Value" = "Değer";
+
+/* stats.py: Status dialog title; In files: stats.py:418; */
+"Ships" = "Gemiler";
+
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} sürüm mevcut";
diff --git a/L10n/zh-Hans.strings b/L10n/zh-Hans.strings
index 2b9eb75d..03e263dd 100644
--- a/L10n/zh-Hans.strings
+++ b/L10n/zh-Hans.strings
@@ -226,13 +226,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "默认";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "自动";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "普通版";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "测试版";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/requirements.txt b/requirements.txt
index 398041a1..b6a7482f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
-certifi==2023.11.17
+certifi==2024.2.2
 requests==2.31.0
 # requests depends on this now ?
-charset-normalizer==2.1.1
+charset-normalizer==3.3.2
 
 watchdog==3.0.0
 # Commented out because this doesn't package well with py2exe

From 674413b36f50deb76466fd0822c2b12ec325a3f9 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Mon, 15 Apr 2024 16:51:43 -0400
Subject: [PATCH 45/49] Update Version String and Changelog

---
 ChangeLog.md       | 24 ++++++++++++++++++++++++
 config/__init__.py |  2 +-
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index 6ee5749f..949b6bf8 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -6,6 +6,30 @@ This is the master changelog for Elite Dangerous Market Connector.  Entries are
       in the source (not distributed with the Windows installer) for the
       currently used version.
 ---
+Release 5.10.4
+===
+This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also 
+adds Turkish translations to EDMC!
+
+We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
+For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
+
+**Changes and Enhancements**
+* Adds Turkish Translations to EDMC
+* Adds DockingDenied and DockingGranted EDDN Schemas
+* Updated FDevIDs Dependency
+* Updated Translations
+* Updated modules files to process several missing module types used for bug squishing or going fast
+* Updated Python Dependencies
+
+**Bug Fixes**
+* Fixed a bug on older Python versions which couldn't import updated type annotations
+
+**Plugin Developers**
+* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
+* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
+and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
+
 Release 5.10.3
 ===
 This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations. 
diff --git a/config/__init__.py b/config/__init__.py
index d85956a7..27d9b450 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -53,7 +53,7 @@ appcmdname = 'EDMC'
 # <https://semver.org/#semantic-versioning-specification-semver>
 # Major.Minor.Patch(-prerelease)(+buildmetadata)
 # NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
-_static_appversion = '5.11.0-alpha0'
+_static_appversion = '5.10.4'
 
 _cached_version: semantic_version.Version | None = None
 copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'

From 3b2f37f868df1116253142ddfb68451bde4dc6a5 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Mon, 15 Apr 2024 16:51:43 -0400
Subject: [PATCH 46/49] Update Version String and Changelog

---
 ChangeLog.md       | 24 ++++++++++++++++++++++++
 config/__init__.py |  3 +--
 2 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/ChangeLog.md b/ChangeLog.md
index 6ee5749f..949b6bf8 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -6,6 +6,30 @@ This is the master changelog for Elite Dangerous Market Connector.  Entries are
       in the source (not distributed with the Windows installer) for the
       currently used version.
 ---
+Release 5.10.4
+===
+This release contains updated dependencies, modules files, translations, and adds two new EDDN schemas. It also 
+adds Turkish translations to EDMC!
+
+We now sign our code! This does mean that built EXEs are now slightly modified on our developer's machines.
+For information on what this means, and opt-out options, please visit https://github.com/EDCD/EDMarketConnector/wiki/Code-Signing-and-EDMC
+
+**Changes and Enhancements**
+* Adds Turkish Translations to EDMC
+* Adds DockingDenied and DockingGranted EDDN Schemas
+* Updated FDevIDs Dependency
+* Updated Translations
+* Updated modules files to process several missing module types used for bug squishing or going fast
+* Updated Python Dependencies
+
+**Bug Fixes**
+* Fixed a bug on older Python versions which couldn't import updated type annotations
+
+**Plugin Developers**
+* modules.p and ships.p are deprecated, and slated for removal in 5.11+!
+* The `openurl()` function in ttkHyperlinkLabel has been deprecated,
+and slated for removal in 5.11+! Please migrate to `webbrowser.open()`.
+
 Release 5.10.3
 ===
 This release contains a bugfix for the shipyard outfitting parsing system and an update to the French translations. 
diff --git a/config/__init__.py b/config/__init__.py
index 3acb2edd..1dfe26c3 100644
--- a/config/__init__.py
+++ b/config/__init__.py
@@ -54,8 +54,7 @@ appcmdname = 'EDMC'
 # <https://semver.org/#semantic-versioning-specification-semver>
 # Major.Minor.Patch(-prerelease)(+buildmetadata)
 # NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
-_static_appversion = '5.10.3'
-
+_static_appversion = '5.10.4'
 _cached_version: semantic_version.Version | None = None
 copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
 

From 1f90ca94092012aa35078a5f0bcb06a4deabbfbf Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Mon, 15 Apr 2024 21:48:30 -0400
Subject: [PATCH 47/49] [Translations] Update Translation Files

---
 L10n/cs.strings         |   6 +-
 L10n/de.strings         |  20 +-
 L10n/es.strings         |   6 +-
 L10n/it.strings         |  20 +-
 L10n/ja.strings         |   6 +-
 L10n/ko.strings         |   6 +-
 L10n/pl.strings         |   6 +-
 L10n/pt-BR.strings      |   6 +-
 L10n/pt-PT.strings      |   6 +-
 L10n/ru.strings         |  35 +-
 L10n/sr-Latn-BA.strings |   6 +-
 L10n/sr-Latn.strings    |  20 +-
 L10n/tr.strings         | 805 ++++++++++++++++++++++++++++++++++++++++
 L10n/zh-Hans.strings    |   6 +-
 14 files changed, 915 insertions(+), 39 deletions(-)
 create mode 100644 L10n/tr.strings

diff --git a/L10n/cs.strings b/L10n/cs.strings
index ba11dc39..071b5e69 100644
--- a/L10n/cs.strings
+++ b/L10n/cs.strings
@@ -178,13 +178,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Výchozí";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/de.strings b/L10n/de.strings
index 2b7c72c4..be36b877 100644
--- a/L10n/de.strings
+++ b/L10n/de.strings
@@ -231,6 +231,18 @@
 /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
 "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Eins oder mehr deiner aktivierten Plugins konnte nicht geladen werden. Du kannst dir die Liste im '{PLUGINS}'-Tab unter '{FILE}' > '{SETTINGS}' ansehen. Dies könnte an einer falschen Ordnerstruktur liegen. Die Datei load.py sollte sich unter plugins/PLUGIN_NAME/load.py befinden.\n\nDu kannst ein Plugin deaktivieren, indem du dessen Ordner ein '{DISABLED}' am Ende des Namens anhängst.";
 
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Einer oder mehrere deiner URL-Anbieter waren ungültig und wurden zurückgesetzt:\n\n";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} war zuvor auf {OLDPROV} festgelegt und wurde zu {NEWPROV} zurückgesetzt.\n";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Standardanbieter wurden zurückgesetzt";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Warte auf vollständige CMDR Anmeldung";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "Journal-Ordner bereits gesperrt";
 
@@ -246,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Standard";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -768,3 +780,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Schiffe";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} ist verfügbar";
diff --git a/L10n/es.strings b/L10n/es.strings
index d2cb7b4b..18874ccf 100644
--- a/L10n/es.strings
+++ b/L10n/es.strings
@@ -184,13 +184,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Por defecto";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/it.strings b/L10n/it.strings
index e5f6ece6..c1cd9b33 100644
--- a/L10n/it.strings
+++ b/L10n/it.strings
@@ -231,6 +231,18 @@
 /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
 "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Impossibile caricare uno o più plugin abilitati. Consulta l'elenco nella scheda '{PLUGINS}' di '{FILE}' > '{SETTINGS}'. Ciò potrebbe essere causato da un'errata struttura delle cartelle. Il file load.py dovrebbe trovarsi in plugins/PLUGIN_NAME/load.py.\n\nPuoi disabilitare un plugin rinominando la sua cartella in modo che abbia '{DISABLED}' alla fine del nome.";
 
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Uno o più Provider URL non erano validi e sono stati ripristinati:\n";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} era impostato su {OLDPROV} ed è stato reimpostato su {NEWPROV}\n";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Reset dei provider predefiniti";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "In attesa del login completo al CMDR";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "cartella Journal già bloccata";
 
@@ -246,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Predefinito";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normale";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -789,3 +801,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Navi";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} è disponibile";
diff --git a/L10n/ja.strings b/L10n/ja.strings
index 68a9fd34..82d9f8df 100644
--- a/L10n/ja.strings
+++ b/L10n/ja.strings
@@ -246,13 +246,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "デフォルト";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "自動";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "通常版";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "ベータ版";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/ko.strings b/L10n/ko.strings
index 577695b0..e29ad74f 100644
--- a/L10n/ko.strings
+++ b/L10n/ko.strings
@@ -202,13 +202,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "기본값";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "자동";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "일반";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "베타";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/pl.strings b/L10n/pl.strings
index 72a66718..547406b1 100644
--- a/L10n/pl.strings
+++ b/L10n/pl.strings
@@ -234,13 +234,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Domyślne";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normalny";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings
index e719101a..605e0559 100644
--- a/L10n/pt-BR.strings
+++ b/L10n/pt-BR.strings
@@ -234,13 +234,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Padrão";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Automático";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings
index ea292b14..0fd5b585 100644
--- a/L10n/pt-PT.strings
+++ b/L10n/pt-PT.strings
@@ -246,13 +246,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Predefinido";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/ru.strings b/L10n/ru.strings
index c3c0fae1..19449e21 100644
--- a/L10n/ru.strings
+++ b/L10n/ru.strings
@@ -213,12 +213,36 @@
 /* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
 "One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Один или несколько ваших подключённых плагинов ещё не имеют поддержки Python 3.x. Пожалуйста, ознакомьтесь со списком во вкладке '{PLUGINS}' '{FILE}' > '{SETTINGS}'. Вы должны проверить наличие обновлённой версии, в противном случае предупредите разработчика о необходимости обновления кода на Python 3.x.\n\nВы можете отключить плагин, переименовав его папку в '{DISABLED}'.";
 
+/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
+"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID файлы не найдены! Некоторые функции, связанные с товарами, могут быть отключены.\n\nВы хотите открыть страницу Wiki о том, как настроить вспомогательные модули?";
+
+/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
+"FDevIDs: Missing Commodity Files" = "FDevIDs: отсутствуют файлы товаров";
+
 /* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
 "Plugins" = "Плагины";
 
 /* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
 "EDMC: Plugins Without Python 3.x Support" = "EDMC: Без поддержки Python 3.x";
 
+/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
+"EDMC: Broken Plugins" = "EDMC: Неработающие плагины";
+
+/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
+"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Не удалось загрузить один или несколько включенных плагинов. Пожалуйста, посмотрите список на вкладке '{PLUGINS}' в разделе '{FILE}' > '{SETTINGS}'. Причиной может быть неправильная структура папок. Файл load.py должен находиться в папке plugins/PLUGIN_NAME/load.py.\n\nВы можете отключить плагин, переименовав его папку так, чтобы в конце названия стояло '{DISABLED}'.";
+
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Один или несколько ваших URL-провайдеров оказались некорректными и были сброшены:";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} был установлен на {OLDPROV} и был сброшен на {NEWPROV}";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: сброс провайдеров по умолчанию";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Ожидание полного входа в систему";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "Каталог журнала уже заблокирован";
 
@@ -234,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "По умолчанию";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Автоматический";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Стандартный";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Бета";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -471,6 +495,9 @@
 /* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
 "Information on migrating plugins" = "Информация о мигрирующих плагинах";
 
+/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
+"Broken Plugins" = "Неработающие плагины";
+
 /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
 "Disabled Plugins" = "Отключенные плагины";
 
@@ -774,3 +801,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Корабли";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} доступен";
diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings
index 2950f8b8..f541416a 100644
--- a/L10n/sr-Latn-BA.strings
+++ b/L10n/sr-Latn-BA.strings
@@ -246,13 +246,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Standardna";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Automatski";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normalni";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings
index 83da541a..e801d067 100644
--- a/L10n/sr-Latn.strings
+++ b/L10n/sr-Latn.strings
@@ -231,6 +231,18 @@
 /* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
 "One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Jedan ili više vaših aktivnih pluginova se nije učitao. Molimo pogledajte listu na '{PLUGINS}' tabu u okviru '{FILE}' > '{SETTINGS}'. Obo vi moglo da se desi zbog pogrešne strukture foldera. load.py fajl bi trebao da se nalazi unutar plugins/PLUGIN_NAME/load.py.\n\nPojedinačni plugin možete deaktivirati promenom imena njegovog foldera dodavanjem '{DISABLED}' na kraju.";
 
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "Jedan ili više vaših URL Provajdera su pogrešni, pa su resetovani:\n";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} je bio postavljen na {OLDPROV} i sada je resetovan na {NEWPROV}\n";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Podrazumevani Provajderi resetovani";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Čekam da se CMDR potpuno uloguje";
+
 /* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
 "Journal directory already locked" = "Žurnal direktorijum je već zaključan";
 
@@ -246,13 +258,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "Podrazumevano";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "Auto";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "Normal";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "Beta";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
@@ -789,3 +801,5 @@
 /* stats.py: Status dialog title; In files: stats.py:418; */
 "Ships" = "Brodovi";
 
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} je dostupna";
diff --git a/L10n/tr.strings b/L10n/tr.strings
new file mode 100644
index 00000000..9d37dfa1
--- /dev/null
+++ b/L10n/tr.strings
@@ -0,0 +1,805 @@
+/* edsm.py:Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:316; */
+"Send flight log and CMDR status to EDSM" = "Uçuş günlüğünü ve CMDR durumunu EDSM'e gönder";
+
+/* prefs.py:Label on button used to open a filesystem folder; In files: prefs.py:706; */
+"Open Log Folder" = "Günlük Klasörünü Aç";
+
+/* inara.py:Text Inara Show API key; In files: inara.py:305; */
+"Show API Key" = "API Anahtarını Göster";
+/* Language name */
+"!Language" = "Türkçe";
+
+/* companion.py: Frontier CAPI didn't respond; In files: companion.py:226; */
+"Error: Frontier CAPI didn't respond" = "Hata: Frontier CAPI yanıt vermedi";
+
+/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:245; */
+"Error: Frontier server is lagging" = "Hata: Frontier sunucusu gecikmesi";
+
+/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:261; */
+"Docked but unknown station: EDO Settlement?" = "Kenetlendi ancak bilinmeyen istasyon: EDO yerleşimi?";
+
+/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:271; */
+"Error: Invalid Credentials" = "Hata: Geçersiz Kimlik";
+
+/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:296; */
+"Error: Wrong Cmdr" = "Hata: Yanlış CMDR";
+
+/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:432; companion.py:517; */
+"Error" = "Hata";
+
+/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:475; companion.py:480; */
+"Error: Couldn't check token customer_id" = "Hata: token kontrol edilemedi customer_id";
+
+/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:486; */
+"Error: customer_id doesn't match!" = "Hata: customer_id uyuşmuyor!";
+
+/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
+"Error: unable to get token" = "Hata: token erişilemedi";
+
+/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
+"Frontier CAPI down for maintenance" = "Frontier CAPI bakım için kapalı durumda";
+
+/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:856; */
+"Frontier CAPI query failure" = "Frontier CAPI sorgulama hatası";
+
+/* EDMarketConnector.py: Main UI Update button; EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:601; EDMarketConnector.py:919; EDMarketConnector.py:1748; */
+"Update" = "Güncelle";
+
+/* EDMarketConnector.py: Appearance - Label for checkbox to select if application always on top; prefs.py: Appearance - Label for checkbox to select if application always on top; In files: EDMarketConnector.py:710; prefs.py:875; */
+"Always on top" = "Her zaman üstte";
+
+/* EDMarketConnector.py: Unknown suit; In files: EDMarketConnector.py:837; */
+"Unknown" = "Bilinmeyen";
+
+/* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:906; */
+"Error: Check E:D journal file location" = "Hata: E:D günlük dosyası konumunu kontrol edin";
+
+/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:913; edsm.py:332; stats.py:57; theme.py:290; */
+"Cmdr" = "Cmdr";
+
+/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:915; EDMarketConnector.py:1487; */
+"Role" = "Rol";
+
+/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:915; EDMarketConnector.py:1497; EDMarketConnector.py:1520; stats.py:405; */
+"Ship" = "Gemi";
+
+/* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:916; */
+"Suit" = "Süit";
+
+/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:917; prefs.py:614; stats.py:407; */
+"System" = "Sistem";
+
+/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:918; prefs.py:632; prefs.py:770; stats.py:408; */
+"Station" = "İstasyon";
+
+/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:921; EDMarketConnector.py:939; EDMarketConnector.py:942; EDMarketConnector.py:2264; */
+"File" = "Dosya";
+
+/* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:922; EDMarketConnector.py:940; EDMarketConnector.py:943; */
+"Edit" = "Düzenle";
+
+/* EDMarketConnector.py: 'View' menu title on OSX; In files: EDMarketConnector.py:923; */
+"View" = "Görüntüle";
+
+/* EDMarketConnector.py: 'Window' menu title on OSX; In files: EDMarketConnector.py:924; */
+"Window" = "Pencere";
+
+/* EDMarketConnector.py: Help' menu title on OSX; EDMarketConnector.py: 'Help' menu title; In files: EDMarketConnector.py:925; EDMarketConnector.py:941; EDMarketConnector.py:944; */
+"Help" = "Yardım";
+
+/* EDMarketConnector.py: App menu entry on OSX; EDMarketConnector.py: Help > About App; In files: EDMarketConnector.py:928; EDMarketConnector.py:959; EDMarketConnector.py:1804; */
+"About {APP}" = "Hakkında {APP}";
+
+/* EDMarketConnector.py: Help > Check for Updates...; In files: EDMarketConnector.py:930; EDMarketConnector.py:958; */
+"Check for Updates..." = "Güncellemeleri Denetle...";
+
+/* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:931; EDMarketConnector.py:948; */
+"Save Raw Data..." = "Ham Verileri Kaydet...";
+
+/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:932; EDMarketConnector.py:947; stats.py:402; */
+"Status" = "Durum";
+
+/* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:933; EDMarketConnector.py:953; */
+"Documentation" = "Dokümantasyon";
+
+/* EDMarketConnector.py: Help > Troubleshooting; In files: EDMarketConnector.py:934; EDMarketConnector.py:954; */
+"Troubleshooting" = "Sorun giderme";
+
+/* EDMarketConnector.py: Help > Report A Bug; In files: EDMarketConnector.py:935; EDMarketConnector.py:955; */
+"Report A Bug" = "Hata Bildir";
+
+/* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:936; EDMarketConnector.py:956; */
+"Privacy Policy" = "Gizlilik Politikası";
+
+/* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:937; EDMarketConnector.py:957; EDMarketConnector.py:1838; */
+"Release Notes" = "Sürüm notları";
+
+/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:949; EDMarketConnector.py:2265; prefs.py:241; */
+"Settings" = "Ayarlar";
+
+/* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:950; */
+"Exit" = "Çıkış";
+
+/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:962; ttkHyperlinkLabel.py:53; */
+"Copy" = "Kopyala";
+
+/* EDMarketConnector.py: CAPI auth aborted because of killswitch; EDMarketConnector.py: CAPI auth query aborted because of killswitch; In files: EDMarketConnector.py:973; EDMarketConnector.py:1067; */
+"CAPI auth disabled by killswitch" = "CAPI kimlik doğrulaması killswitch tarafından devre dışı bırakıldı";
+
+/* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:978; */
+"Logging in..." = "Giriş yapılıyor...";
+
+/* EDMarketConnector.py: Successfully authenticated with the Frontier website; In files: EDMarketConnector.py:994; EDMarketConnector.py:1657; */
+"Authentication successful" = "Kimlik doğrulama başarılı";
+
+/* EDMarketConnector.py: Player is not docked at a station, when we expect them to be; In files: EDMarketConnector.py:1025; */
+"You're not docked at a station!" = "Bir istasyona kenetli değilsiniz!";
+
+/* EDMarketConnector.py: Status - Either no market or no modules data for station from Frontier CAPI; In files: EDMarketConnector.py:1033; */
+"Station doesn't have anything!" = "İstasyon içeriği mevcut değil.";
+
+/* EDMarketConnector.py: Status - No station market data from Frontier CAPI; In files: EDMarketConnector.py:1038; */
+"Station doesn't have a market!" = "İstasyon'da market bulunmuyor!";
+
+/* EDMarketConnector.py: CAPI queries aborted because Cmdr name is unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because Cmdr name is unknown; In files: EDMarketConnector.py:1077; EDMarketConnector.py:1164; */
+"CAPI query aborted: Cmdr name unknown" = "CAPI sorgusu iptal edildi: Cmdr adı bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because game mode unknown; In files: EDMarketConnector.py:1083; */
+"CAPI query aborted: Game mode unknown" = "CAPI sorgusu iptal edildi: Oyun modu bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because GameVersion unknown; EDMarketConnector.py: CAPI fleetcarrier query aborted because GameVersion unknown; In files: EDMarketConnector.py:1089; EDMarketConnector.py:1170; */
+"CAPI query aborted: GameVersion unknown" = "CAPI sorgusu iptal edildi: GameVersion bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because current star system name unknown; In files: EDMarketConnector.py:1095; */
+"CAPI query aborted: Current system unknown" = "CAPI sorgusu iptal edildi: Mevcut sistem bilinmiyor";
+
+/* EDMarketConnector.py: CAPI queries aborted because player is in multi-crew on other Cmdr's ship; In files: EDMarketConnector.py:1101; */
+"CAPI query aborted: In other-ship multi-crew" = "CAPI sorgusu iptal edildi: Başka gemi çoklu-mürettebatı";
+
+/* EDMarketConnector.py: CAPI queries aborted because player is in CQC (Arena); In files: EDMarketConnector.py:1107; */
+"CAPI query aborted: CQC (Arena) detected" = "CAPI sorgusu iptal edildi: CQC (Arena) tespit edildi";
+
+/* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1128; EDMarketConnector.py:1179; */
+"Fetching data..." = "Veri işleniyor...";
+
+/* EDMarketConnector.py: CAPI fleetcarrier query aborted because of killswitch; In files: EDMarketConnector.py:1157; */
+"CAPI fleetcarrier disabled by killswitch" = "CAPI filo taşıyıcısı killswitch tarafından devre dışı bırakıldı";
+
+/* EDMarketConnector.py: No data was returned for the fleetcarrier from the Frontier CAPI; In files: EDMarketConnector.py:1219; */
+"CAPI: No fleetcarrier data returned" = "CAPI: Filo taşıyıcı verisi gelmedi.";
+
+/* EDMarketConnector.py: We didn't have the fleetcarrier callsign when we should have; In files: EDMarketConnector.py:1223; */
+"CAPI: Fleetcarrier data incomplete" = "CAPI: Filo taşıyıcı verileri eksik";
+
+/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1242; */
+"CAPI: No commander data returned" = "CAPI: Cmdr verisi döndürülmedi";
+
+/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1246; stats.py:333; */
+"Who are you?!" = "Sen kimsin?!";
+
+/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1252; stats.py:341; */
+"Where are you?!" = "Neredesin?!";
+
+/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1259; stats.py:349; */
+"What are you flying?!" = "Ne uçuruyorsun?!";
+
+/* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1384; */
+"Frontier CAPI server error" = "Frontier CAPI sunucu hatası";
+
+/* EDMarketConnector.py: Frontier CAPI Access Token expired, trying to get a new one; In files: EDMarketConnector.py:1390; */
+"CAPI: Refreshing access token..." = "CAPI: Erişim token yenileniyor...";
+
+/* EDMarketConnector.py: Time when we last obtained Frontier CAPI data; In files: EDMarketConnector.py:1434; */
+"Last updated at %H:%M:%S" = "Son güncelleme %H:%M:%S";
+
+/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1462; */
+"Fighter" = "Avcı";
+
+/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1463; */
+"Gunner" = "Nişancı";
+
+/* EDMarketConnector.py: Multicrew role; In files: EDMarketConnector.py:1464; */
+"Helm" = "Dümen";
+
+/* EDMarketConnector.py: Cooldown on 'Update' button; In files: EDMarketConnector.py:1742; */
+"cooldown {SS}s" = "bekleme süresi {SS}s";
+
+/* EDMarketConnector.py: Generic 'OK' button label; prefs.py: 'OK' button on Settings/Preferences window; In files: EDMarketConnector.py:1864; prefs.py:292; */
+"OK" = "Tamam";
+
+/* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1936; */
+"Shutting down..." = "Kapanıyor...";
+
+/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2253:2259; */
+"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası henüz Python 3.x desteğine sahip değil.  '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Güncellenmiş bir sürümün mevcut olup olmadığını kontrol edin, aksi takdirde geliştiriciyi Python 3.x kodunu güncellemesi gerektiği konusunda uyarmalısınız.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz.";
+
+/* EDMarketConnector.py: Popup-text about missing FDEVID Files; In files: EDMarketConnector.py:2329; */
+"FDevID Files not found! Some functionality regarding commodities may be disabled.\r\n\r\n Do you want to open the Wiki page on how to set up submodules?" = "FDevID Dosyaları bulunamadı! Ürünlerle ilgili bazı işlevler devre dışı bırakılabilir.\n\nAlt modüllerin nasıl kurulacağına ilişkin Wiki sayfasını açmak ister misiniz?";
+
+/* EDMarketConnector.py: Popup window title for missing FDEVID files; In files: EDMarketConnector.py:2340; */
+"FDevIDs: Missing Commodity Files" = "FDevIDs: Eksik Ürün Dosyaları";
+
+/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2263; prefs.py:986; */
+"Plugins" = "Eklentiler";
+
+/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2274; */
+"EDMC: Plugins Without Python 3.x Support" = "EDMC: Python 3.x Desteği Olmayan Eklentiler";
+
+/* EDMarketConnector.py: Popup window title for list of 'broken' plugins that failed to load; In files: EDMarketConnector.py:2285; */
+"EDMC: Broken Plugins" = "EDMC: Bozuk Eklentiler";
+
+/* EDMarketConnector.py: Popup-text about 'broken' plugins that failed to load; In files: EDMarketConnector.py:2266; */
+"One or more of your enabled plugins failed to load. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. This could be caused by a wrong folder structure. The load.py file should be located under plugins/PLUGIN_NAME/load.py.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "Etkin eklentilerinizden bir veya daha fazlası yüklenemedi. '{PLUGINS}' sekmesindeki '{FILE}' > '{SETTINGS}' bölümünde bulunan listeyi bulun. Sorunun sebebi yanlış klasör yapısı olabilir. Load.py dosyası, plugins/PLUGIN_NAME/load.py altında bulunmalıdır.\n\nBir eklentiyi, klasörünü adının sonunda '{DISABLED}' olacak şekilde yeniden adlandırarak devre dışı bırakabilirsiniz.";
+
+/* EDMarketConnector.py: Popup-text about Reset Providers; In files: EDMarketConnector.py:2146; */
+"One or more of your URL Providers were invalid, and have been reset:\r\n\r\n" = "URL Sağlayıcılarınızdan bir veya daha fazlası geçersizdi ve sıfırlandı:";
+
+/* EDMarketConnector.py: Text About What Provider Was Reset; In files: EDMarketConnector.py:2148; */
+"{PROVIDER} was set to {OLDPROV}, and has been reset to {NEWPROV}\r\n" = "{PROVIDER} , {OLDPROV}'a ayarlanmıştı ancak {NEWPROV}'a sıfırlandı";
+
+/* EDMarketConnector.py: Popup window title for Reset Providers; In files: EDMarketConnector.py:2161; */
+"EDMC: Default Providers Reset" = "EDMC: Varsayılan Sağlayıcıların Sıfırlanması";
+
+/* EDMarketConnector.py: Await Full CMDR Login to Game; In files: EDMarketConnector.py:813; */
+"Awaiting Full CMDR Login" = "Tam CMDR Girişi Bekleniyor";
+
+/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:208; */
+"Journal directory already locked" = "Günlük dizini zaten kilitli";
+
+/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:225:226; */
+"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Yeni Günlük Dizini konumu zaten kilitli.{CR}Çözüm için Tekrar deneyebilir veya Yoksay'ı seçebilirsiniz.";
+
+/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:230; */
+"Retry" = "Tekrar Dene";
+
+/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:234; */
+"Ignore" = "Yoksay";
+
+/* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
+"Default" = "Varsayılan";
+
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
+"Auto" = "Otomatik";
+
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
+"Normal" = "Normal";
+
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
+"Beta" = "Beta";
+
+/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */
+"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Coriolis.io gemi ekipman tasarımları ile kullanılacak URL'yi ayarlayın. Link'in '/import?data=' ile bitmesi GEREKTİĞİNİ unutmayın.";
+
+/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:97; */
+"Normal URL" = "Normal URL";
+
+/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:100; coriolis.py:109; */
+"Reset" = "Sıfırla";
+
+/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:106; */
+"Beta URL" = "Beta URL";
+
+/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:116; */
+"Override Beta/Normal Selection" = "Beta/Normal Seçimi Geçersiz Kıl";
+
+/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:156; */
+"Invalid Coriolis override mode!" = "Geçersiz Coriolis geçersiz kılma modu!";
+
+/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:458; eddn.py:2413; eddn.py:2451; eddn.py:2519; */
+"Error: Can't connect to EDDN" = "Hata: EDDN'e bağlanılamıyor";
+
+/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:576; */
+"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Hatası: EDMC sürümü EDDN için çok eski. Lütfen güncelle.";
+
+/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:582; */
+"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Hatası: Doğrulama Başarısız Oldu (EDMC sürümü eski olabilir?). Günlüğe bakın";
+
+/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:587; */
+"EDDN Error: Returned {STATUS} status code" = "EDDN Hatası: {STATUS} durum kodu ile döndü.";
+
+/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:2041; */
+"Send station data to the Elite Dangerous Data Network" = "İstasyon verilerini Elite Dangerous Data Network'e gönderin";
+
+/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:2052; */
+"Send system and scan data to the Elite Dangerous Data Network" = "Sistem ve tarama verilerini Elite Dangerous Data Network'e gönderin";
+
+/* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:2063; */
+"Delay sending until docked" = "Kenetlenmeye kadar gönderimi ertele";
+
+/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:2178; */
+"EDDN journal handler disabled. See Log." = "EDDN günlük düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
+
+/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:2507; */
+"Sending data to EDDN..." = "Veriler EDDN'e gönderiliyor...";
+
+/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:319; */
+"Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map kimlik bilgileri";
+
+/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:341; */
+"Commander Name" = "Cmdr ismi";
+
+/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:350; inara.py:278; */
+"API Key" = "API Anahtarı";
+
+/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:394; prefs.py:527; prefs.py:1157; prefs.py:1190; stats.py:154; stats.py:173; stats.py:192; stats.py:209; */
+"None" = "Hiçbiri";
+
+/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:516; */
+"EDSM Handler disabled. See Log." = "EDSM düzenleyici devre dışı. Kayıt geçmişini kontrol edin.";
+
+/* edsm.py: EDSM - Only Live data; In files: edsm.py:632; */
+"EDSM only accepts Live galaxy data" = "EDSM sadece canlı galaxy verilerini kabul eder";
+
+/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:916; edsm.py:1048; */
+"Error: EDSM {MSG}" = "Hata: EDSM {MSG}";
+
+/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:953; edsm.py:1043; */
+"Error: Can't connect to EDSM" = "Hata: EDSM'e bağlanılamıyor";
+
+/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:257; */
+"Send flight log and Cmdr status to Inara" = "Uçuş günlüğünü ve Cmdr durumunu Inara'ya gönder";
+
+/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:269; */
+"Inara credentials" = "Inara kimlik bilgileri";
+
+/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; inara.py: Inara - Only Live data; In files: inara.py:384; inara.py:386; */
+"Inara only accepts Live galaxy data" = "Inara sadece canlı galaxy verilerini kabul eder";
+
+/* inara.py: INARA support disabled via killswitch; In files: inara.py:395; */
+"Inara disabled. See Log." = "Inara devre dışı. Kayıt geçmişini kontrol edin.";
+
+/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1650; inara.py:1663; */
+"Error: Inara {MSG}" = "Hata: Inara {MSG}";
+
+/* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:237; */
+"Preferences" = "Tercihler";
+
+/* prefs.py: Settings > Output - choosing what data to save to files; In files: prefs.py:335; */
+"Please choose what data to save" = "Lütfen hangi verilerin kaydedileceğini seçin";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:341; */
+"Market data in CSV format file" = "CSV formatında Market verileri";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:350; */
+"Market data in Trade Dangerous format file" = "Trade Dangerous formatında Market verileri";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:360; */
+"Ship loadout" = "Gemi Ekipmanları";
+
+/* prefs.py: Settings > Output option; In files: prefs.py:370; */
+"Automatically update on docking" = "Kenetlenme sırasında otomatik güncelle";
+
+/* prefs.py: Settings > Output - Label for "where files are located"; In files: prefs.py:379; prefs.py:398; */
+"File location" = "Dosya konumu";
+
+/* prefs.py: macOS Preferences - files location selection button; In files: prefs.py:387; prefs.py:437; */
+"Change..." = "Değiştir...";
+
+/* prefs.py: NOT-macOS Settings - files location selection button; prefs.py: NOT-macOS Setting - files location selection button; In files: prefs.py:390; prefs.py:440; */
+"Browse..." = "Gezin...";
+
+/* prefs.py: Label for 'Output' Settings/Preferences tab; In files: prefs.py:405; */
+"Output" = "Kayıt";
+
+/* prefs.py: Settings > Configuration - Label for Journal files location; In files: prefs.py:431; prefs.py:446; */
+"E:D journal file location" = "E:D günlük dosya konumu";
+
+/* prefs.py: Settings > Configuration - Label for CAPI section; In files: prefs.py:469; */
+"CAPI Settings" = "CAPI ayarları";
+
+/* prefs.py: Configuration - Enable or disable the Fleet Carrier CAPI calls; In files: prefs.py:475; */
+"Enable Fleetcarrier CAPI Queries" = "Filo Taşıyıcı CAPI sorgulamalarını etkinleştir";
+
+/* prefs.py: Hotkey/Shortcut settings prompt on OSX; In files: prefs.py:490; */
+"Keyboard shortcut" = "Klavye Kısayolu";
+
+/* prefs.py: Hotkey/Shortcut settings prompt on Windows; In files: prefs.py:492; */
+"Hotkey" = "Kısayoltuşu";
+
+/* prefs.py: macOS Preferences > Configuration - restart the app message; In files: prefs.py:501; */
+"Re-start {APP} to use shortcuts" = "Kısayolları kullanmak için {APP}'i yeniden başlat";
+
+/* prefs.py: macOS - Configuration - need to grant the app permission for keyboard shortcuts; In files: prefs.py:510; */
+"{APP} needs permission to use shortcuts" = "{APP}'nin kısayolları kullanabilmesi için izne ihtiyacı var";
+
+/* prefs.py: Shortcut settings button on OSX; In files: prefs.py:515; */
+"Open System Preferences" = "Sistem Tercihlerini Aç";
+
+/* prefs.py: Configuration - Act on hotkey only when ED is in foreground; In files: prefs.py:538; */
+"Only when Elite: Dangerous is the active app" = "Sadece Elite: Dangerous aktif uygulama olduğunda";
+
+/* prefs.py: Configuration - play sound when hotkey used; In files: prefs.py:549; */
+"Play sound" = "Ses çal";
+
+/* prefs.py: Configuration - disable checks for app updates when in-game; In files: prefs.py:564; */
+"Disable Automatic Application Updates Check when in-game" = "Otomatik Uygulama Güncellemelerini Oyun sırasında Devre Dışı Bırak";
+
+/* prefs.py: Label for preferred shipyard, system and station 'providers'; In files: prefs.py:577; */
+"Preferred websites" = "Tercih edilen web siteleri";
+
+/* prefs.py: Label for Shipyard provider selection; In files: prefs.py:588; */
+"Shipyard" = "Tersane";
+
+/* prefs.py: Label for checkbox to utilise alternative Coriolis URL method; In files: prefs.py:600; */
+"Use alternate URL method" = "Alternatif URL yöntemi kullan";
+
+/* prefs.py: Configuration - Label for selection of Log Level; In files: prefs.py:653; */
+"Log Level" = "Günlük Seviyesi";
+
+/* prefs.py: Label for 'Configuration' tab in Settings; In files: prefs.py:681; */
+"Configuration" = "Yapılandırma";
+
+/* prefs.py: UI elements privacy section header in privacy tab of preferences; In files: prefs.py:690; */
+"Main UI privacy options" = "Ana Arayüz gizlilik seçenekleri";
+
+/* prefs.py: Hide private group owner name from UI checkbox; In files: prefs.py:695; */
+"Hide private group name in UI" = "Özel grup ismini arayüzde gizle";
+
+/* prefs.py: Hide multicrew captain name from main UI checkbox; In files: prefs.py:699; */
+"Hide multi-crew captain name" = "Çoklu-mürettebat ismini gizle";
+
+/* prefs.py: Preferences privacy tab title; In files: prefs.py:703; */
+"Privacy" = "Gizlilik";
+
+/* prefs.py: Label for Settings > Appeareance > selection of 'normal' text colour; In files: prefs.py:716; */
+"Normal text" = "Normal metin";
+
+/* prefs.py: Label for Settings > Appeareance > selection of 'highlightes' text colour; In files: prefs.py:718; */
+"Highlighted text" = "Vurgulanan metin";
+
+/* prefs.py: Appearance - Label for selection of application display language; In files: prefs.py:727; */
+"Language" = "Dil";
+
+/* prefs.py: Label for Settings > Appearance > Theme selection; In files: prefs.py:737; */
+"Theme" = "Tema";
+
+/* prefs.py: Label for 'Dark' theme radio button; In files: prefs.py:749; */
+"Dark" = "Karanlık";
+
+/* prefs.py: Label for 'Transparent' theme radio button; In files: prefs.py:756; */
+"Transparent" = "Şeffaflık";
+
+/* prefs.py: Appearance - Label for selection of UI scaling; In files: prefs.py:802; */
+"UI Scale Percentage" = "Arayüz ölçek yüzdesi";
+
+/* prefs.py: Appearance - Help/hint text for UI scaling selection; In files: prefs.py:823; */
+"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 Varsayılan değerdir. Değişikliklerin geçerli olması için {CR}Yeniden Başlatma Gereklidir{CR}!";
+
+/* prefs.py: Appearance - Label for selection of main window transparency; In files: prefs.py:833; */
+"Main window transparency" = "Ana pencere şeffaflığı";
+
+/* prefs.py: Appearance - Help/hint text for Main window transparency selection; In files: prefs.py:853:856; */
+"100 means fully opaque.{CR}Window is updated in real time" = "100 tamamen opak anlamına gelir.{CR}Pencere gerçek zamanlı olarak güncellenir";
+
+/* prefs.py: Appearance option for Windows "minimize to system tray"; In files: prefs.py:885; */
+"Minimize to system tray" = "Simge durumuna küçült";
+
+/* prefs.py: Label for Settings > Appearance tab; In files: prefs.py:893; */
+"Appearance" = "Görünüm";
+
+/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
+"Plugins folder" = "Eklentiler klasörü";
+
+/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
+"Open" = "Aç";
+
+/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
+"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "İpucu: Bir eklentiyi, klasör adına{CR}'{EXT}' ekleyerek devre dışı bırakabilirsiniz";
+
+/* prefs.py: Label on list of enabled plugins; In files: prefs.py:934; */
+"Enabled Plugins" = "Etkin Eklentiler";
+
+/* prefs.py: Plugins - Label for list of 'enabled' plugins that don't work with Python 3.x; In files: prefs.py:954; */
+"Plugins Without Python 3.x Support" = "Python 3.x Desteği Olmayan Eklentiler";
+
+/* prefs.py: Plugins - Label on URL to documentation about migrating plugins from Python 2.7; In files: prefs.py:962; */
+"Information on migrating plugins" = "Eklentilerin taşınmasıyla ilgili bilgiler";
+
+/* prefs.py: Plugins - Label for list of 'broken' plugins that failed to load; In files: prefs.py:1039; */
+"Broken Plugins" = "Bozuk Eklentiler";
+
+/* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:977; */
+"Disabled Plugins" = "Devre Dışı Eklentiler";
+
+/* stats.py: Cmdr stats; In files: stats.py:58; */
+"Balance" = "Bakiye";
+
+/* stats.py: Cmdr stats; In files: stats.py:59; */
+"Loan" = "Borç";
+
+/* stats.py: Top rank; In files: stats.py:63; */
+"Elite" = "Elite";
+
+/* stats.py: Top rank +1; In files: stats.py:64; */
+"Elite I" = "Elite I";
+
+/* stats.py: Top rank +2; In files: stats.py:65; */
+"Elite II" = "Elite II";
+
+/* stats.py: Top rank +3; In files: stats.py:66; */
+"Elite III" = "Elite III";
+
+/* stats.py: Top rank +4; In files: stats.py:67; */
+"Elite IV" = "Elite IV";
+
+/* stats.py: Top rank +5; In files: stats.py:68; */
+"Elite V" = "Elite V";
+
+/* stats.py: Ranking; In files: stats.py:74; */
+"Combat" = "Combat";
+
+/* stats.py: Ranking; In files: stats.py:75; */
+"Trade" = "Trade";
+
+/* stats.py: Ranking; In files: stats.py:76; */
+"Explorer" = "Explorer";
+
+/* stats.py: Ranking; In files: stats.py:77; */
+"Mercenary" = "Mercenary";
+
+/* stats.py: Ranking; In files: stats.py:78; */
+"Exobiologist" = "Exobiologist";
+
+/* stats.py: Ranking; In files: stats.py:79; */
+"CQC" = "CQC";
+
+/* stats.py: Ranking; In files: stats.py:80; */
+"Federation" = "Federation";
+
+/* stats.py: Ranking; In files: stats.py:81; */
+"Empire" = "Empire";
+
+/* stats.py: Ranking; In files: stats.py:82; */
+"Powerplay" = "Powerplay";
+
+/* stats.py: Combat rank; In files: stats.py:91; */
+"Harmless" = "Harmless";
+
+/* stats.py: Combat rank; In files: stats.py:92; */
+"Mostly Harmless" = "Mostly Harmless";
+
+/* stats.py: Combat rank; In files: stats.py:93; */
+"Novice" = "Novice";
+
+/* stats.py: Combat rank; In files: stats.py:94; */
+"Competent" = "Competent";
+
+/* stats.py: Combat rank; In files: stats.py:95; */
+"Expert" = "Expert";
+
+/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:96; stats.py:176; */
+"Master" = "Master";
+
+/* stats.py: Combat rank; In files: stats.py:97; */
+"Dangerous" = "Dangerous";
+
+/* stats.py: Combat rank; In files: stats.py:98; */
+"Deadly" = "Deadly";
+
+/* stats.py: Trade rank; In files: stats.py:101; */
+"Penniless" = "Penniless";
+
+/* stats.py: Trade rank; In files: stats.py:102; */
+"Mostly Penniless" = "Mostly Penniless";
+
+/* stats.py: Trade rank; In files: stats.py:103; */
+"Peddler" = "Peddler";
+
+/* stats.py: Trade rank; In files: stats.py:104; */
+"Dealer" = "Dealer";
+
+/* stats.py: Trade rank; In files: stats.py:105; */
+"Merchant" = "Merchant";
+
+/* stats.py: Trade rank; In files: stats.py:106; */
+"Broker" = "Broker";
+
+/* stats.py: Trade rank; In files: stats.py:107; */
+"Entrepreneur" = "Entrepreneur";
+
+/* stats.py: Trade rank; In files: stats.py:108; */
+"Tycoon" = "Tycoon";
+
+/* stats.py: Explorer rank; In files: stats.py:111; */
+"Aimless" = "Aimless";
+
+/* stats.py: Explorer rank; In files: stats.py:112; */
+"Mostly Aimless" = "Mostly Aimless";
+
+/* stats.py: Explorer rank; In files: stats.py:113; */
+"Scout" = "Scout";
+
+/* stats.py: Explorer rank; In files: stats.py:114; */
+"Surveyor" = "Surveyor";
+
+/* stats.py: Explorer rank; In files: stats.py:115; */
+"Trailblazer" = "Trailblazer";
+
+/* stats.py: Explorer rank; In files: stats.py:116; */
+"Pathfinder" = "Pathfinder";
+
+/* stats.py: Explorer rank; In files: stats.py:117; */
+"Ranger" = "Ranger";
+
+/* stats.py: Explorer rank; In files: stats.py:118; */
+"Pioneer" = "Pioneer";
+
+/* stats.py: Mercenary rank; In files: stats.py:122; */
+"Defenceless" = "Defenceless";
+
+/* stats.py: Mercenary rank; In files: stats.py:123; */
+"Mostly Defenceless" = "Mostly Defenceless";
+
+/* stats.py: Mercenary rank; In files: stats.py:124; */
+"Rookie" = "Rookie";
+
+/* stats.py: Mercenary rank; In files: stats.py:125; */
+"Soldier" = "Soldier";
+
+/* stats.py: Mercenary rank; In files: stats.py:126; stats.py:128; */
+"Gunslinger" = "Gunslinger";
+
+/* stats.py: Mercenary rank; In files: stats.py:127; */
+"Warrior" = "Warrior";
+
+/* stats.py: Mercenary rank; In files: stats.py:129; */
+"Deadeye" = "Deadeye";
+
+/* stats.py: Exobiologist rank; In files: stats.py:132; */
+"Directionless" = "Directionless";
+
+/* stats.py: Exobiologist rank; In files: stats.py:133; */
+"Mostly Directionless" = "Mostly Directionless";
+
+/* stats.py: Exobiologist rank; In files: stats.py:134; */
+"Compiler" = "Compiler";
+
+/* stats.py: Exobiologist rank; In files: stats.py:135; */
+"Collector" = "Collector";
+
+/* stats.py: Exobiologist rank; In files: stats.py:136; */
+"Cataloguer" = "Cataloguer";
+
+/* stats.py: Exobiologist rank; In files: stats.py:137; */
+"Taxonomist" = "Taxonomist";
+
+/* stats.py: Exobiologist rank; In files: stats.py:138; */
+"Ecologist" = "Ecologist";
+
+/* stats.py: Exobiologist rank; In files: stats.py:139; */
+"Geneticist" = "Geneticist";
+
+/* stats.py: CQC rank; In files: stats.py:142; */
+"Helpless" = "Helpless";
+
+/* stats.py: CQC rank; In files: stats.py:143; */
+"Mostly Helpless" = "Mostly Helpless";
+
+/* stats.py: CQC rank; In files: stats.py:144; */
+"Amateur" = "Amateur";
+
+/* stats.py: CQC rank; In files: stats.py:145; */
+"Semi Professional" = "Semi Professional";
+
+/* stats.py: CQC rank; In files: stats.py:146; */
+"Professional" = "Professional";
+
+/* stats.py: CQC rank; In files: stats.py:147; */
+"Champion" = "Champion";
+
+/* stats.py: CQC rank; In files: stats.py:148; */
+"Hero" = "Hero";
+
+/* stats.py: CQC rank; In files: stats.py:149; */
+"Gladiator" = "Gladiator";
+
+/* stats.py: Federation rank; In files: stats.py:155; */
+"Recruit" = "Recruit";
+
+/* stats.py: Federation rank; In files: stats.py:156; */
+"Cadet" = "Cadet";
+
+/* stats.py: Federation rank; In files: stats.py:157; */
+"Midshipman" = "Midshipman";
+
+/* stats.py: Federation rank; In files: stats.py:158; */
+"Petty Officer" = "Petty Officer";
+
+/* stats.py: Federation rank; In files: stats.py:159; */
+"Chief Petty Officer" = "Chief Petty Officer";
+
+/* stats.py: Federation rank; In files: stats.py:160; */
+"Warrant Officer" = "Warrant Officer";
+
+/* stats.py: Federation rank; In files: stats.py:161; */
+"Ensign" = "Ensign";
+
+/* stats.py: Federation rank; In files: stats.py:162; */
+"Lieutenant" = "Lieutenant";
+
+/* stats.py: Federation rank; In files: stats.py:163; */
+"Lieutenant Commander" = "Lieutenant Commander";
+
+/* stats.py: Federation rank; In files: stats.py:164; */
+"Post Commander" = "Post Commander";
+
+/* stats.py: Federation rank; In files: stats.py:165; */
+"Post Captain" = "Post Captain";
+
+/* stats.py: Federation rank; In files: stats.py:166; */
+"Rear Admiral" = "Rear Admiral";
+
+/* stats.py: Federation rank; In files: stats.py:167; */
+"Vice Admiral" = "Vice Admiral";
+
+/* stats.py: Federation rank; In files: stats.py:168; */
+"Admiral" = "Admiral";
+
+/* stats.py: Empire rank; In files: stats.py:174; */
+"Outsider" = "Outsider";
+
+/* stats.py: Empire rank; In files: stats.py:175; */
+"Serf" = "Serf";
+
+/* stats.py: Empire rank; In files: stats.py:177; */
+"Squire" = "Squire";
+
+/* stats.py: Empire rank; In files: stats.py:178; */
+"Knight" = "Knight";
+
+/* stats.py: Empire rank; In files: stats.py:179; */
+"Lord" = "Lord";
+
+/* stats.py: Empire rank; In files: stats.py:180; */
+"Baron" = "Baron";
+
+/* stats.py: Empire rank; In files: stats.py:181; */
+"Viscount" = "Viscount";
+
+/* stats.py: Empire rank; In files: stats.py:182; */
+"Count" = "Count";
+
+/* stats.py: Empire rank; In files: stats.py:183; */
+"Earl" = "Earl";
+
+/* stats.py: Empire rank; In files: stats.py:184; */
+"Marquis" = "Marquis";
+
+/* stats.py: Empire rank; In files: stats.py:185; */
+"Duke" = "Duke";
+
+/* stats.py: Empire rank; In files: stats.py:186; */
+"Prince" = "Prince";
+
+/* stats.py: Empire rank; In files: stats.py:187; */
+"King" = "King";
+
+/* stats.py: Power rank; In files: stats.py:193; */
+"Rating 1" = "Reyting 1";
+
+/* stats.py: Power rank; In files: stats.py:194; */
+"Rating 2" = "Reyting 2";
+
+/* stats.py: Power rank; In files: stats.py:195; */
+"Rating 3" = "Reyting 3";
+
+/* stats.py: Power rank; In files: stats.py:196; */
+"Rating 4" = "Reyting 4";
+
+/* stats.py: Power rank; In files: stats.py:197; */
+"Rating 5" = "Reyting 5";
+
+/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:315; */
+"Status: Don't yet know your Commander name" = "Durum: Cmdr adınızı henüz bilmiyorum";
+
+/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:323; */
+"Status: No CAPI data yet" = "Durum: Henüz CAPI verisi yok";
+
+/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:409; */
+"Value" = "Değer";
+
+/* stats.py: Status dialog title; In files: stats.py:418; */
+"Ships" = "Gemiler";
+
+/* update.py: Update Available Text; In files: update.py:229; */
+"{NEWVER} is available" = "{NEWVER} sürüm mevcut";
diff --git a/L10n/zh-Hans.strings b/L10n/zh-Hans.strings
index 2b9eb75d..03e263dd 100644
--- a/L10n/zh-Hans.strings
+++ b/L10n/zh-Hans.strings
@@ -226,13 +226,13 @@
 /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:193; prefs.py:455; prefs.py:709; prefs.py:742; */
 "Default" = "默认";
 
-/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:74; coriolis.py:77; coriolis.py:123; coriolis.py:139; coriolis.py:145; */
+/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:48; coriolis.py:74; coriolis.py:77; coriolis.py:94; coriolis.py:123; coriolis.py:139; coriolis.py:145; coriolis.py:179; coriolis.py:182; */
 "Auto" = "自动";
 
-/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:75; coriolis.py:121; coriolis.py:137; */
+/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:49; coriolis.py:75; coriolis.py:95; coriolis.py:121; coriolis.py:137; coriolis.py:180; */
 "Normal" = "普通版";
 
-/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:76; coriolis.py:122; coriolis.py:138; */
+/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:50; coriolis.py:76; coriolis.py:96; coriolis.py:122; coriolis.py:138; coriolis.py:181; */
 "Beta" = "测试版";
 
 /* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:91:93; */

From 164a92092f349a1889564f480db711e90f167b7a Mon Sep 17 00:00:00 2001
From: github-actions <github-actions@github.com>
Date: Wed, 17 Apr 2024 12:25:52 +0000
Subject: [PATCH 48/49] updating submodules

---
 FDevIDs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/FDevIDs b/FDevIDs
index 7cffab3d..9b3f4061 160000
--- a/FDevIDs
+++ b/FDevIDs
@@ -1 +1 @@
-Subproject commit 7cffab3d913b788f981923687203399c22cf358f
+Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2

From 9cd69a19e4b7a0cfeb89045044aec8a85d4dccd5 Mon Sep 17 00:00:00 2001
From: David Sangrey <rixxan@hullseals.space>
Date: Wed, 17 Apr 2024 15:35:57 -0400
Subject: [PATCH 49/49] [1133] Refine Inheritence

---
 myNotebook.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/myNotebook.py b/myNotebook.py
index 2442acbe..070b28e1 100644
--- a/myNotebook.py
+++ b/myNotebook.py
@@ -67,7 +67,7 @@ class EntryMenu(ttk.Entry):
     """Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
 
     def __init__(self, *args, **kwargs) -> None:
-        super().__init__(*args, **kwargs)
+        ttk.Entry.__init__(self, *args, **kwargs)
 
         self.menu = tk.Menu(self, tearoff=False)
         self.menu.add_command(label="Copy", command=self.copy)
@@ -120,7 +120,7 @@ class EntryMenu(ttk.Entry):
             pass
 
 
-class Entry(EntryMenu or ttk.Entry):  # type: ignore
+class Entry(EntryMenu):
     """Custom ttk.Entry class to fix some display issues."""
 
     # DEPRECATED: Migrate to EntryMenu. Will remove in 5.12 or later.