Skip to content

Commit 080d9f9

Browse files
committed
[1812] Handover Translations
1 parent 6732515 commit 080d9f9

15 files changed

+415
-423
lines changed

EDMC.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
if TYPE_CHECKING:
2828
from logging import TRACE # type: ignore # noqa: F401 # needed to make mypy happy
29-
def _(x: str): return x
3029

3130
edmclogger.set_channels_loglevel(logging.INFO)
3231

@@ -35,7 +34,7 @@ def _(x: str): return x
3534
import commodity
3635
import companion
3736
import edshipyard
38-
import l10n
37+
from l10n import translations as tr
3938
import loadout
4039
import outfitting
4140
import shipyard
@@ -66,7 +65,7 @@ def log_locale(prefix: str) -> None:
6665
)
6766

6867

69-
l10n.Translations.install_dummy()
68+
tr.install_dummy()
7069

7170
SERVER_RETRY = 5 # retry pause for Companion servers [s]
7271
EXIT_SUCCESS, EXIT_SERVER, EXIT_CREDENTIALS, EXIT_VERIFICATION, EXIT_LAGGING, EXIT_SYS_ERR, EXIT_ARGS, \
@@ -164,7 +163,7 @@ def main(): # noqa: C901, CCR001
164163
newversion: EDMCVersion | None = updater.check_appcast()
165164
if newversion:
166165
# LANG: Update Available Text
167-
newverstr: str = _("{NEWVER} is available").format(NEWVER=newversion.title)
166+
newverstr: str = tr.tl("{NEWVER} is available").format(NEWVER=newversion.title)
168167
print(f'{appversion()} ({newverstr})')
169168
else:
170169
print(appversion())

EDMarketConnector.py

+87-90
Large diffs are not rendered by default.

companion.py

+14-15
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,11 @@
3737
from edmc_data import companion_category_map as category_map
3838
from EDMCLogging import get_main_logger
3939
from monitor import monitor
40+
from l10n import translations as tr
4041

4142
logger = get_main_logger()
4243

4344
if TYPE_CHECKING:
44-
def _(x: str): return x
45-
4645
UserDict = collections.UserDict[str, Any] # indicate to our type checkers what this generic class holds normally
4746
else:
4847
UserDict = collections.UserDict # Otherwise simply use the actual class
@@ -224,7 +223,7 @@ def __init__(self, *args) -> None:
224223
self.args = args
225224
if not args:
226225
# LANG: Frontier CAPI didn't respond
227-
self.args = (_("Error: Frontier CAPI didn't respond"),)
226+
self.args = (tr.tl("Error: Frontier CAPI didn't respond"),)
228227

229228

230229
class ServerConnectionError(ServerError):
@@ -243,7 +242,7 @@ def __init__(self, *args) -> None:
243242
self.args = args
244243
if not args:
245244
# LANG: Frontier CAPI data doesn't agree with latest Journal game location
246-
self.args = (_('Error: Frontier server is lagging'),)
245+
self.args = (tr.tl('Error: Frontier server is lagging'),)
247246

248247

249248
class NoMonitorStation(Exception):
@@ -259,7 +258,7 @@ def __init__(self, *args) -> None:
259258
self.args = args
260259
if not args:
261260
# LANG: Commander is docked at an EDO settlement, got out and back in, we forgot the station
262-
self.args = (_("Docked but unknown station: EDO Settlement?"),)
261+
self.args = (tr.tl("Docked but unknown station: EDO Settlement?"),)
263262

264263

265264
class CredentialsError(Exception):
@@ -269,7 +268,7 @@ def __init__(self, *args) -> None:
269268
self.args = args
270269
if not args:
271270
# LANG: Generic "something went wrong with Frontier Auth" error
272-
self.args = (_('Error: Invalid Credentials'),)
271+
self.args = (tr.tl('Error: Invalid Credentials'),)
273272

274273

275274
class CredentialsRequireRefresh(Exception):
@@ -294,7 +293,7 @@ def __init__(self, *args) -> None:
294293
self.args = args
295294
if not args:
296295
# LANG: Frontier CAPI authorisation not for currently game-active commander
297-
self.args = (_('Error: Wrong Cmdr'),)
296+
self.args = (tr.tl('Error: Wrong Cmdr'),)
298297

299298

300299
class Auth:
@@ -429,7 +428,7 @@ def authorize(self, payload: str) -> str: # noqa: CCR001
429428
'<unknown error>'
430429
)
431430
# LANG: Generic error prefix - following text is from Frontier auth service
432-
raise CredentialsError(f'{_("Error")}: {error!r}')
431+
raise CredentialsError(f'{tr.tl("Error")}: {error!r}')
433432

434433
r = None
435434
try:
@@ -472,18 +471,18 @@ def authorize(self, payload: str) -> str: # noqa: CCR001
472471
if (usr := data_decode.get('usr')) is None:
473472
logger.error('No "usr" in /decode data')
474473
# LANG: Frontier auth, no 'usr' section in returned data
475-
raise CredentialsError(_("Error: Couldn't check token customer_id"))
474+
raise CredentialsError(tr.tl("Error: Couldn't check token customer_id"))
476475

477476
if (customer_id := usr.get('customer_id')) is None:
478477
logger.error('No "usr"->"customer_id" in /decode data')
479478
# LANG: Frontier auth, no 'customer_id' in 'usr' section in returned data
480-
raise CredentialsError(_("Error: Couldn't check token customer_id"))
479+
raise CredentialsError(tr.tl("Error: Couldn't check token customer_id"))
481480

482481
# All 'FID' seen in Journals so far have been 'F<id>'
483482
# Frontier, Steam and Epic
484483
if f'F{customer_id}' != monitor.state.get('FID'):
485484
# LANG: Frontier auth customer_id doesn't match game session FID
486-
raise CredentialsError(_("Error: customer_id doesn't match!"))
485+
raise CredentialsError(tr.tl("Error: customer_id doesn't match!"))
487486

488487
logger.info(f'Frontier CAPI Auth: New token for \"{self.cmdr}\"')
489488
cmdrs = config.get_list('cmdrs', default=[])
@@ -505,7 +504,7 @@ def authorize(self, payload: str) -> str: # noqa: CCR001
505504
self.dump(r)
506505

507506
# LANG: Failed to get Access Token from Frontier Auth service
508-
raise CredentialsError(_('Error: unable to get token')) from e
507+
raise CredentialsError(tr.tl('Error: unable to get token')) from e
509508

510509
logger.error(f"Frontier CAPI Auth: Can't get token for \"{self.cmdr}\"")
511510
self.dump(r)
@@ -514,7 +513,7 @@ def authorize(self, payload: str) -> str: # noqa: CCR001
514513
'<unknown error>'
515514
)
516515
# LANG: Generic error prefix - following text is from Frontier auth service
517-
raise CredentialsError(f'{_("Error")}: {error!r}')
516+
raise CredentialsError(f'{tr.tl("Error")}: {error!r}')
518517

519518
@staticmethod
520519
def invalidate(cmdr: str | None) -> None:
@@ -841,7 +840,7 @@ def capi_single_query(
841840
except Exception as e:
842841
logger.debug('Attempting GET', exc_info=e)
843842
# LANG: Frontier CAPI data retrieval failed
844-
raise ServerError(f'{_("Frontier CAPI query failure")}: {capi_endpoint}') from e
843+
raise ServerError(f'{tr.tl("Frontier CAPI query failure")}: {capi_endpoint}') from e
845844

846845
if capi_endpoint == self.FRONTIER_CAPI_PATH_PROFILE and 'commander' not in capi_data:
847846
logger.error('No commander in returned data')
@@ -874,7 +873,7 @@ def handle_http_error(response: requests.Response, endpoint: str):
874873
if response.status_code == 418:
875874
# "I'm a teapot" - used to signal maintenance
876875
# LANG: Frontier CAPI returned 418, meaning down for maintenance
877-
raise ServerError(_("Frontier CAPI down for maintenance"))
876+
raise ServerError(tr.tl("Frontier CAPI down for maintenance"))
878877

879878
logger.exception('Frontier CAPI: Misc. Error')
880879
raise ServerError('Frontier CAPI: Misc. Error')

journal_lock.py

+8-11
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@
1313
from enum import Enum
1414
from os import getpid as os_getpid
1515
from tkinter import ttk
16-
from typing import TYPE_CHECKING, Callable
17-
16+
from typing import Callable
17+
from l10n import translations as tr
1818
from config import config
1919
from EDMCLogging import get_main_logger
2020

2121
logger = get_main_logger()
2222

23-
if TYPE_CHECKING: # pragma: no cover
24-
def _(x: str) -> str:
25-
return x
26-
2723

2824
class JournalLockResult(Enum):
2925
"""Enumeration of possible outcomes of trying to lock the Journal Directory."""
@@ -212,7 +208,7 @@ def __init__(self, parent: tk.Tk, callback: Callable) -> None:
212208
self.parent = parent
213209
self.callback = callback
214210
# LANG: Title text on popup when Journal directory already locked
215-
self.title(_('Journal directory already locked'))
211+
self.title(tr.tl('Journal directory already locked'))
216212

217213
# remove decoration
218214
if sys.platform == 'win32':
@@ -225,16 +221,17 @@ def __init__(self, parent: tk.Tk, callback: Callable) -> None:
225221

226222
self.blurb = tk.Label(frame)
227223
# LANG: Text for when newly selected Journal directory is already locked
228-
self.blurb['text'] = _("The new Journal Directory location is already locked.{CR}"
229-
"You can either attempt to resolve this and then Retry, or choose to Ignore this.")
224+
self.blurb['text'] = tr.tl("The new Journal Directory location is already locked.{CR}"
225+
"You can either attempt to resolve this and then Retry, "
226+
"or choose to Ignore this.")
230227
self.blurb.grid(row=1, column=0, columnspan=2, sticky=tk.NSEW)
231228

232229
# LANG: Generic 'Retry' button label
233-
self.retry_button = ttk.Button(frame, text=_('Retry'), command=self.retry)
230+
self.retry_button = ttk.Button(frame, text=tr.tl('Retry'), command=self.retry)
234231
self.retry_button.grid(row=2, column=0, sticky=tk.EW)
235232

236233
# LANG: Generic 'Ignore' button label
237-
self.ignore_button = ttk.Button(frame, text=_('Ignore'), command=self.ignore)
234+
self.ignore_button = ttk.Button(frame, text=tr.tl('Ignore'), command=self.ignore)
238235
self.ignore_button.grid(row=2, column=1, sticky=tk.EW)
239236
self.protocol("WM_DELETE_WINDOW", self._destroy)
240237

l10n.py

+37-10
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
from config import config
2424
from EDMCLogging import get_main_logger
2525

26-
if TYPE_CHECKING:
27-
def _(x: str) -> str: return x
28-
2926
# Note that this is also done in EDMarketConnector.py, and thus removing this here may not have a desired effect
3027
try:
3128
locale.setlocale(locale.LC_ALL, '')
@@ -61,7 +58,16 @@ def _(x: str) -> str: return x
6158
GetNumberFormatEx.restype = ctypes.c_int
6259

6360

64-
class _Translations:
61+
class Translations:
62+
"""
63+
The Translation System.
64+
65+
Contains all the logic needed to support multiple languages in EDMC.
66+
DO NOT USE THIS DIRECTLY UNLESS YOU KNOW WHAT YOU'RE DOING.
67+
In most cases, you'll want to import translations.
68+
69+
For most cases: from l10n import translations as tr.
70+
"""
6571

6672
FALLBACK = 'en' # strings in this code are in English
6773
FALLBACK_NAME = 'English'
@@ -79,6 +85,8 @@ def install_dummy(self) -> None:
7985
Use when translation is not desired or not available
8086
"""
8187
self.translations = {None: {}}
88+
# WARNING: '_' is Deprecated. Will be removed in 6.0 or later.
89+
# Migrate to calling Translations.translate directly.
8290
builtins.__dict__['_'] = lambda x: str(x).replace(r'\"', '"').replace('{CR}', '\n')
8391

8492
def install(self, lang: str | None = None) -> None: # noqa: CCR001
@@ -88,7 +96,7 @@ def install(self, lang: str | None = None) -> None: # noqa: CCR001
8896
:param lang: The language to translate to, defaults to the preferred language
8997
"""
9098
available = self.available()
91-
available.add(_Translations.FALLBACK)
99+
available.add(Translations.FALLBACK)
92100
if not lang:
93101
# Choose the default language
94102
for preferred in Locale.preferred_languages():
@@ -122,6 +130,8 @@ def install(self, lang: str | None = None) -> None: # noqa: CCR001
122130
except Exception:
123131
logger.exception(f'Exception occurred while parsing {lang}.strings in plugin {plugin}')
124132

133+
# WARNING: '_' is Deprecated. Will be removed in 6.0 or later.
134+
# Migrate to calling Translations.translate directly.
125135
builtins.__dict__['_'] = self.translate
126136

127137
def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
@@ -135,12 +145,12 @@ def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
135145

136146
for line in h:
137147
if line.strip():
138-
match = _Translations.TRANS_RE.match(line)
148+
match = Translations.TRANS_RE.match(line)
139149
if match:
140150
to_set = match.group(2).replace(r'\"', '"').replace('{CR}', '\n')
141151
translations[match.group(1).replace(r'\"', '"')] = to_set
142152

143-
elif not _Translations.COMMENT_RE.match(line):
153+
elif not Translations.COMMENT_RE.match(line):
144154
logger.debug(f'Bad translation: {line.strip()}')
145155
h.close()
146156

@@ -149,6 +159,10 @@ def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]:
149159

150160
return translations
151161

162+
def tl(self, x: str, context: str | None = None) -> str:
163+
"""Use the shorthand Dummy loader for the translate function."""
164+
return self.translate(x, context)
165+
152166
def translate(self, x: str, context: str | None = None) -> str:
153167
"""
154168
Translate the given string to the current lang.
@@ -182,11 +196,11 @@ def available_names(self) -> dict[str | None, str]:
182196
"""Available language names by code."""
183197
names: dict[str | None, str] = {
184198
# LANG: The system default language choice in Settings > Appearance
185-
None: _('Default'), # Appearance theme and language setting
199+
None: self.tl('Default'), # Appearance theme and language setting
186200
}
187201
names.update(sorted(
188202
[(lang, self.contents(lang).get(LANGUAGE_ID, lang)) for lang in self.available()] +
189-
[(_Translations.FALLBACK, _Translations.FALLBACK_NAME)],
203+
[(Translations.FALLBACK, Translations.FALLBACK_NAME)],
190204
key=lambda x: x[1]
191205
)) # Sort by name
192206

@@ -324,8 +338,21 @@ def preferred_languages(self) -> Iterable[str]:
324338

325339
# singletons
326340
Locale = _Locale()
327-
Translations = _Translations()
341+
translations = Translations()
342+
343+
344+
# WARNING: 'Translations' singleton is deprecated. Will be removed in 6.0 or later.
345+
# Migrate to importing 'translations'.
346+
# Begin Deprecation Zone
347+
class _Translations(Translations):
348+
def __init__(self):
349+
logger.warning(DeprecationWarning('Translations and _Translations() are deprecated. '
350+
'Please use translations and Translations() instead.'))
351+
super().__init__()
352+
328353

354+
Translations: Translations = _Translations() # type: ignore # Yes, I know this is awful. But we need it for compat.
355+
# End Deprecation Zone
329356

330357
# generate template strings file - like xgettext
331358
# parsing is limited - only single ' or " delimited strings, and only one string per line

monitor.py

-4
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@
3434
MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds
3535
MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds
3636

37-
if TYPE_CHECKING:
38-
def _(x: str) -> str:
39-
return x
40-
4137
if sys.platform == 'win32':
4238
import ctypes
4339
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR

0 commit comments

Comments
 (0)