From a863cebc661e01869863deeb4ccd201d133fd4bf Mon Sep 17 00:00:00 2001 From: aussig Date: Mon, 1 Apr 2024 07:48:15 +0100 Subject: [PATCH 1/4] [2188] Add optional lang parameter to l10n.Translations.translate() to allow language to be overridden. --- PLUGINS.md | 103 +++++++++++++++++++++++++++++------------------------ l10n.py | 39 +++++++++++++++----- 2 files changed, 86 insertions(+), 56 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 3048f345f..5800efb0d 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -31,7 +31,7 @@ then you'll need to be using an appropriate version of Python. The current version is listed in the [Environment section of Releasing.md](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md#environment). If you're developing your plugin simply against an install of EDMarketConnector -then you'll be relying on the bundled version of Python (it's baked +then you'll be relying on the bundled version of Python (it's baked into the .exe via the py2exe build process). Please be sure to read the [Avoiding potential pitfalls](#avoiding-potential-pitfalls) @@ -40,21 +40,21 @@ EDMarketConnector code including whole application crashes. ## Being aware of core application changes -It is highly advisable to ensure you are aware of all EDMarketConnector -releases, including the pre-releases. The -beta and -rc changelogs will +It is highly advisable to ensure you are aware of all EDMarketConnector +releases, including the pre-releases. The -beta and -rc changelogs will contain valuable information about any forthcoming changes that affect plugins. The easiest way is: 1. Login to [GitHub](https://github.com). 2. Navigate to [EDMarketConnector](https://github.com/EDCD/EDMarketConnector). - 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on - us). It's currently (2021-05-13) the left-most button of 3 near the + 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on + us). It's currently (2021-05-13) the left-most button of 3 near the top-right of the page. 4. Click 'Custom'. 5. Ensure 'Releases' is selected. 6. Click 'Apply'. -And, of course, either ensure you check your GitHub messages regularly, or +And, of course, either ensure you check your GitHub messages regularly, or have it set up to email you such notifications. You should also keep an eye on [our GitHub Discussions](https://github.com/EDCD/EDMarketConnector/discussions) @@ -113,13 +113,13 @@ from the original files unless specified as allowed in this section. Use `monitor.game_running()` as follows in case a plugin needs to know if we think the game is running. *NB: This is a function, and should be called as such. Using the bare word `game_running` will always be `True`.* - + ``` from monitor import monitor ... if monitor.game_running(): ... -``` +``` Use `monitor.is_live_galaxy()` to determine if the player is playing in the Live galaxy. Note the implementation details of this. At time of writing it @@ -135,7 +135,7 @@ append a string to call out your plugin if you wish). `from ttkHyperlinkLabel import HyperlinkLabel` and `import myNotebook as nb` - For creating UI elements. -In addition to the above we also explicitly package the following python +In addition to the above we also explicitly package the following python modules for plugin use: - shutil @@ -252,7 +252,7 @@ include variables, and even the returns of functions, in the output. ## Checking core EDMC version -If you have code that needs to act differently under different versions of +If you have code that needs to act differently under different versions of this application then you can check utilise `config.appversion`. Prior to version 5.0.0 this was a simple string. From 5.0.0 onwards it is, @@ -313,7 +313,7 @@ Mac, and `$TMP/EDMarketConnector.log` on Linux. ## Avoiding potential pitfalls -There are a number of things that your code should either do or avoiding +There are a number of things that your code should either do or avoiding doing so as to play nicely with the core EDMarketConnector code and not risk causing application crashes or hangs. @@ -324,12 +324,12 @@ See the section on [packaging extra modules](#your-plugin-directory-name-must-be ### Use a thread for long-running code -By default, your plugin code will be running in the main thread. So, if you -perform some operation that takes significant time (more than a second) you -will be blocking both the core code from continuing *and* any other plugins +By default, your plugin code will be running in the main thread. So, if you +perform some operation that takes significant time (more than a second) you +will be blocking both the core code from continuing *and* any other plugins from running their main-thread code. -This includes any connections to remote services, such as a website or +This includes any connections to remote services, such as a website or remote database. So please place such code within its own thread. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) @@ -338,20 +338,20 @@ with a queue to send data, and telling the sub-thread to stop during shutdown. ### All tkinter calls in main thread -The only tkinter calls that should ever be made from a sub-thread are +The only tkinter calls that should ever be made from a sub-thread are `event_generate()` calls to send data back to the main thread. -Any attempt to manipulate tkinter UI elements directly from a sub-thread +Any attempt to manipulate tkinter UI elements directly from a sub-thread will most likely crash the whole program. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) -code for an example of using `event_generate()` to cause the plugin main -thread code to update a UI element. Start from the `plugin_app()` +code for an example of using `event_generate()` to cause the plugin main +thread code to update a UI element. Start from the `plugin_app()` implementation. ### Do not call tkinter `event_generate` during shutdown. -However, you must **not** make *any* tkinter `event_generate()` call whilst +However, you must **not** make *any* tkinter `event_generate()` call whilst the application is shutting down. The application shutdown sequence is itself triggered from the `<>` event @@ -359,8 +359,8 @@ handler, and generating another event from any code in, or called from, there causes the application to hang somewhere in the tk libraries. You can detect if the application is shutting down with the boolean -`config.shutting_down`. Note that although this is technically a function -its implementation is of a property on `config.AbstractConfig` and thus you +`config.shutting_down`. Note that although this is technically a function +its implementation is of a property on `config.AbstractConfig` and thus you should treat it as a variable. **Do NOT use**: @@ -372,7 +372,7 @@ should treat it as a variable. # During shutdown ``` -as this will cause the 'During shutdown' branch to *always* be taken, as in +as this will cause the 'During shutdown' branch to *always* be taken, as in this context you're testing if the function exists, and that is always True. So instead use: @@ -417,8 +417,8 @@ your plugin's settings in a platform-independent way. Previously this was done with a single set and two get methods, the new methods provide better type safety. -If you want to maintain compatibility with pre-5.0.0 versions of this -application (please encourage plugin users to update!) then you'll need to +If you want to maintain compatibility with pre-5.0.0 versions of this +application (please encourage plugin users to update!) then you'll need to include this code in at least once in your plugin (no harm in putting it in all modules/files): @@ -699,8 +699,8 @@ cause `state['NavRoute'] = None`, but if you open the galaxy map in-game and cause an automatic re-plot of last route, then a new `NavRoute` event will also be generated and passed to plugins. -[2] - Some data from the CAPI is sometimes returned as a `list` (when all -members are present) and other times as an integer-keyed `dict` (when at +[2] - Some data from the CAPI is sometimes returned as a `list` (when all +members are present) and other times as an integer-keyed `dict` (when at least one member is missing, so the indices are not contiguous). We choose to always convert to the integer-keyed `dict` form so that code utilising the data is simpler. @@ -751,7 +751,7 @@ Journal `ModuleInfo` event. `OnFoot` is an indication as to if the player is on-foot, rather than in a vehicle. -`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your +`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your Odyssey MicroResources in your Ship Locker. `BacKPack` contains `dict`s for the same when you're on-foot. @@ -760,10 +760,10 @@ relating to suits and their loadouts. New in version 5.0.1: -`Odyssey` boolean based on the presence of such a flag in the `LoadGame` +`Odyssey` boolean based on the presence of such a flag in the `LoadGame` event. Defaults to `False`, i.e. if no such key in the event. -The previously undocumented `Horizons` boolean is similarly from `LoadGame`, +The previously undocumented `Horizons` boolean is similarly from `LoadGame`, but blindly retrieves the value rather than having a strict default. There'd be an exception if it wasn't there, and the value would be `None`. Note that this is **NOT** the same as the return from @@ -821,7 +821,7 @@ if that's what was in the file. New in version 5.8.0: -`StarPos`, `SystemAddress`, `SystemName` and `SystemPopulation` have been +`StarPos`, `SystemAddress`, `SystemName` and `SystemPopulation` have been added to the `state` dictionary. Best efforts data pertaining to the star system the player is in. @@ -853,8 +853,8 @@ react to either in your plugin code then either compare in a case insensitive manner or check for both. The difference in case allows you to differentiate between the two scenarios. -**NB: Any of these events are passing to `journal_entry_cqc` rather than to -`journal_entry` if player has loaded into Arena (CQC).** +**NB: Any of these events are passing to `journal_entry_cqc` rather than to +`journal_entry` if player has loaded into Arena (CQC).** This event is not sent when EDMarketConnector is running on a different machine so you should not *rely* on receiving this event. @@ -871,15 +871,15 @@ Examples of this are: 1. Every `NavRoute` event contains the full `Route` array as loaded from `NavRoute.json`. - + *NB: There is no indication available when a player cancels a route.* The game itself does not provide any such, not in a Journal event, not in a `Status.json` flag. - + The Journal documentation v28 is incorrect about the event and file being `Route(.json)` the word is `NavRoute`. Also the format of the data is, e.g. - + ```json { "timestamp":"2021-03-10T11:31:37Z", "event":"NavRoute", @@ -893,9 +893,9 @@ Examples of this are: ``` 1. Every `ModuleInfo` event contains the full data as loaded from the - `ModulesInfo.json` file. Note that we use the singular form here to + `ModulesInfo.json` file. Note that we use the singular form here to stay consistent with the Journal event name. - + --- ### Journal entry in CQC @@ -955,7 +955,7 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]): sys.stderr.write("Hardpoints {}\n".format(is_deployed and "deployed" or "stowed")) ``` -`dashboard_entry()` is called with the latest data from the `Status.json` +`dashboard_entry()` is called with the latest data from the `Status.json` file when an update to that file is detected. This will be when something on the player's cockpit display changes - @@ -1208,6 +1208,15 @@ Wrap each string that needs translating with the `_()` function, e.g.: somewidget["text"] = _("Happy!") ``` +If you wish to override EDMCs current language when translating, +`l10n.Translations.translate()` takes an optional `lang` parameter which can +be passed a language identifier. For example to override all translations +to German: + +```python +_ = functools.partial(l10n.Translations.translate, context=__file__, lang="de") +``` + If you display localized strings in EDMarketConnector's main window you should refresh them in your `prefs_changed` function in case the user has changed their preferred language. @@ -1262,11 +1271,11 @@ Any modules the core application code uses will naturally be packaged, and we explicitly include a small number of additional modules for the use of plugins. -Whilst we would like to make all of the `stdlib` of Python available it is -not automatically packaged into our releases by py2exe. We hope to address -this in the 5.3 release series. In the meantime, if there's anything -missing that you'd like to use, please ask. Yes, this very much means you -need to test your plugins against a Windows installation of the application +Whilst we would like to make all of the `stdlib` of Python available it is +not automatically packaged into our releases by py2exe. We hope to address +this in the 5.3 release series. In the meantime, if there's anything +missing that you'd like to use, please ask. Yes, this very much means you +need to test your plugins against a Windows installation of the application to be sure it will work. See @@ -1416,7 +1425,7 @@ versions of EDMarketConnector: [2to3](https://docs.python.org/3/library/2to3.html) tool can automate much of this work. -We advise *against* making any attempt to have a plugin's code work under -both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based -versions of this application, and you shouldn't support use of them with +We advise *against* making any attempt to have a plugin's code work under +both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based +versions of this application, and you shouldn't support use of them with your plugin. diff --git a/l10n.py b/l10n.py index a2b5185ae..66d66cad4 100755 --- a/l10n.py +++ b/l10n.py @@ -17,9 +17,10 @@ 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, makedirs, pardir, sep +from os.path import abspath, basename, dirname, exists, isdir, isfile, join from typing import TYPE_CHECKING, Iterable, TextIO, cast + from config import config from EDMCLogging import get_main_logger @@ -154,21 +155,41 @@ def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: return translations - def translate(self, x: str, context: str | None = None) -> str: + def translate(self, x: str, context: str | None = None, lang: str | None = None) -> str: # noqa: CCR001 """ - Translate the given string to the current lang. + Translate the given string to the current lang or an overriden lang. :param x: The string to translate - :param context: Whether or not to search the given directory for translation files, defaults to None + :param context: Contains the full path to the file being localised, from which the plugin name is parsed and + used to locate the plugin translation files, defaults to None + :param lang: Contains a language code to override the EDMC language for this translation, defaults to None :return: The translated string """ + plugin_name: str | None = None + plugin_path: str | None = None + if context: # TODO: There is probably a better way to go about this now. - context = context[len(config.plugin_dir)+1:].split(sep)[0] - if self.translations[None] and context not in self.translations: - logger.debug(f'No translations for {context!r}') + plugin_name = context[len(config.plugin_dir)+1:].split(sep)[0] + plugin_path = join(config.plugin_dir_path, plugin_name, LOCALISATION_DIR) + + if lang: + contents: dict[str, str] = self.contents(lang=lang, plugin_path=plugin_path) + + if not contents or type(contents) is not dict: + logger.debug(f'Failure loading translations for overridden language {lang!r}') + return self.translate(x) + elif x not in contents.keys(): + logger.debug(f'Missing translation: {x!r} for overridden language {lang!r}') + return self.translate(x) + else: + return contents.get(x) or self.translate(x) + + if plugin_name: + if self.translations[None] and plugin_name not in self.translations: + logger.debug(f'No translations for {plugin_name!r}') - return self.translations.get(context, {}).get(x) or self.translate(x) + return self.translations.get(plugin_name, {}).get(x) or self.translate(x) if self.translations[None] and x not in self.translations[None]: logger.debug(f'Missing translation: {x!r}') From 158456aba73fc9ee8a548ed2c35c6b9f4fd60b0d Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Fri, 12 Apr 2024 11:08:58 -0400 Subject: [PATCH 2/4] [2198] Remove Potentially Unused Dependencies Let's see what happens! --- FDevIDs | 2 +- config/__init__.py | 2 +- requirements.txt | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/FDevIDs b/FDevIDs index 9b3f40612..7cffab3d9 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2 +Subproject commit 7cffab3d913b788f981923687203399c22cf358f diff --git a/config/__init__.py b/config/__init__.py index 27d9b4506..c9a477c46 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -53,7 +53,7 @@ # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.4' +_static_appversion = '5.11.0-alpha1' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD' diff --git a/requirements.txt b/requirements.txt index 75ea40411..8c4503621 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,5 @@ -certifi==2024.2.2 requests==2.31.0 pillow==10.3.0 -# requests depends on this now ? -charset-normalizer==3.3.2 - watchdog==3.0.0 -# Commented out because this doesn't package well with py2exe 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 From 6b626fc0dabd758892a7975b0adb9ee6ad93790b Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sat, 20 Apr 2024 17:45:14 -0400 Subject: [PATCH 3/4] Update FDevIDs --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index 7cffab3d9..9b3f40612 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit 7cffab3d913b788f981923687203399c22cf358f +Subproject commit 9b3f40612017b43a8b826017e1e2befebd9074f2 From fb6206279a4ba47e171ee12feeba00adc05f9740 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 13 May 2024 12:27:39 +0000 Subject: [PATCH 4/4] updating submodules --- coriolis-data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coriolis-data b/coriolis-data index 8adfd86b6..651aab5af 160000 --- a/coriolis-data +++ b/coriolis-data @@ -1 +1 @@ -Subproject commit 8adfd86b64e8c14e873d2f5123d88ca6743420b9 +Subproject commit 651aab5af6a22980a1f88dcbb9ed256244cd6dff