Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Implement internazionalization for Decky Loader #361

Merged
merged 137 commits into from
May 2, 2023

Conversation

RodoMa92
Copy link
Contributor

@RodoMa92 RodoMa92 commented Feb 2, 2023

As promised, this is my prototype for translating this awesome piece of software.

For translating the codebase, I've chosen i18next as a framework.

Whats seems to work fine:

  1. Backend serves json files corresponding to the Steam user language selected plus a fallback to english by default
  2. Frontend had all strings replaced with the corresponding t() call (see below for details) in order to dynamically update the text in the UI.
  3. English and Italian complete translation are already provided, although I will probably tweak it again as soon as I begin to test a lot more of the UI
  4. Strings are automatically extracted using the action extracttext from vscode. This will scan the codebase, search for t() calls and fill automatically the json translation files with empty values (can be set to whatever we want).
  5. AFAICS settings UI seems already to works perfectly fine.

Possible pitfalls left in this:

  1. I have seen a lot of https://reactjs.org/docs/error-decoder.html/?invariant=321 in the console of chromium while debugging the backend, I might still have left some boobytraps around the codebase, I still need to actually check if they are still there and if they actually are an issue. UPDATE: This is caused by some components missing react hooks inside them (I think), for now I should have disabled the small translation stuff caused by this, and it's working fine now (but not translated). If someone wants to take a stab at it at fixing them, I left the translated part commented out here.
  2. The whole codebase still needs to be heavily tested.

Information to the devs in order to translate text:

  1. First you need to import the main function from i18next:
    import { useTranslation } from 'react-i18next';

  2. After that you need to create a hook inside the corresponding react function like this:
    const { t } = useTranslation();

  3. This will give you the function t that you can use inside react components in order to resolve text like this:
    t('ClassName.corresponding_string'). The first parameter is the identifier for resolving the desired text.

i18next have quite a lot of options for adapting to various languages, that can be specified after the string identifier. I recommend translators to go read the documentation, it's quite succinct and complete in that regard.

  1. Once all parameters have been added to the file, run extracttext. This will parse the codebase and fill up all json files automatically.

If you want to add support for an additional missing language to the codebase, the steps are the following:

  1. Add your language identifier (ex: en-US for american english, it-IT for italian, etc.) in the array at row 47 in the file called i18next-parser.config.mjs.

  2. Run the activity extracttext in visual studio code. This will parse automatically the codebase and generate a corresponding json file in <root_codebase>/backend/locales for your languages, all with parameters set to MISSING (also configurable on demand).

  3. If you can't translate everything, remember to remove the parts of the JSON not translated, in order to allow i18next to fallback to the default english language. Otherwise the UI in that specific language will look full of MISSING everywhere. This is the reason I've dropped the empty language files in the latest commits, to avoid any confusion. If anyone wants to translate it further, you just need to run extracttext again to get all the non translated strings back in the file. Not necessary anymore.

@RodoMa92
Copy link
Contributor Author

I should have fixed everything, let me know if I have missed something.

@AAGaming00
Copy link
Member

I feel like there may be a better way to handle translation in the modals to allow for usage of useTranslation, which I'll look into before merging this

@AAGaming00
Copy link
Member

If you can't translate everything, remember to remove the parts of the JSON not translated.

Is there a way to automate this?

@RodoMa92
Copy link
Contributor Author

RodoMa92 commented Apr 24, 2023

If you can't translate everything, remember to remove the parts of the JSON not translated.

Is there a way to automate this?

In theory you can pass a function to default value that should allow to disable the output if needed.
// You may also specify a function accepting the locale, namespace, key, and value as arguments

I do not know however how this would be handled internally. Haven't looked that far.

EDIT: Few examples in tests: https://github.com/i18next/i18next-parser/blob/7289d29bbeae6a153738c0b6a1b2d7f2494b245c/test/parser.test.js#L1089

@RodoMa92
Copy link
Contributor Author

Ok, found a way if we leave the default of the untranslated strings as blank strings:
https://www.i18next.com/principles/fallback#missing-values-for-existing-keys

@RodoMa92
Copy link
Contributor Author

Just tested and can confirm it works, so now we can export languages even with empty strings.

@RodoMa92
Copy link
Contributor Author

I feel like there may be a better way to handle translation in the modals to allow for usage of useTranslation, which I'll look into before merging this

A few pointers for you:
The only feasible way that I found to change the message programmatically in i18next is to use the count property. There are two ways of doing this:

  • Native count: This is not deterministic, unfortunately (each language might have different ways to enumerate. Some gives a lot of them, other only two). It's not really useful in this case.
  • Using a first party extension link: This allows exactly what we want (we can specify a defined interval and the associating text for each language freely), however I couldn't get to cooperate with the parser, and the defined strings would be seen as not used and removed each time I would run the parser. It's probably possible to coax the parser by specifying additional options to it (probably in i18nextOptions), but I didn't explore this further personally. This is probably the easiest route.
  • Maybe a third party postprocessor? Still, similar issue as above.

Regarding the toaster, I've tried to inject the property in it to no avail, but since this is a interface to plugins, and not only internally to Decky, I'll prefer to leave this to someone who knows the code better than me.

Otherwise this branch has been merged again with the latest master.

@PartyWumpus
Copy link
Member

PartyWumpus commented May 1, 2023

This looks good to merge, I've been using this for a while without issue, and Train's been testing it over the weekend. Will merge (and publish a prerelease with it) if you think it's ready.

@RodoMa92
Copy link
Contributor Author

RodoMa92 commented May 2, 2023

Besides the two things above (the not ideal modal translation selection and the notifications not translated yet, but already integrated in the translation system) I do not see any other missing features or bugs left in this branch. In my opinion it's ready to merge.

@PartyWumpus PartyWumpus merged commit 35e7c80 into SteamDeckHomebrew:main May 2, 2023
4 checks passed
@RodoMa92 RodoMa92 deleted the i18n branch May 4, 2023 04:58
KP2048 pushed a commit to EmuDeck/decky-loader that referenced this pull request May 4, 2023
…omebrew#361)

* First iteration for internationalization of the loader

* First iteration for internationalization of the loader

* Cleanup node mess

* Cleanup node mess pt2

* Additional touches

* Latest decky changed merged into i18n and updated translation.

* Styling fixes

* Initial backend hosting implementation

* Added correct url path of the loopback server.

* Added correct url path of the loopback server.

* Some better namespaced text.

* Added whitelist for locales path.

* Refactor languages and fix hooks logic bugs.

* Small typo in language translation structure.

* Working backend, automatically swtich languages with steam and language fixes.

* Fix to languages

* Key fixes

* Additional language fixes.

* Additional json changes

* Final text revision and added a vscode tasks to automatically extract text from code.

* Typo in the middleware

* Remove unused imports

* Cleanup whitespaces.

* Import changes

* Revert "Import changes"

This reverts commit 8e82319.

* Update index.d.ts

* Clean up unused imports

* Delete pnpm-lock.yaml

* Update rollup.config.js

* Update PluginInstallModal.tsx

* Update index.tsx

* Update plugin-loader.tsx

* Update plugin-loader.tsx

* Revert "Delete pnpm-lock.yaml"

This reverts commit 3a39f36.

* Additional strings reworks.

* Fixes for issues coming from github merge.

* Fixes for master

* Styling fixes

* Styling pt2

* Missed a few strings in master,

* Styling fixes

* Additional master merge fixes.

* Final cleanup and adaptation to master.

* Final empty language cleanup and few string added

* Small changes to italian translation

* Disabled translation on a few components inside plugin-loader for missing react hooks.

* Fixed passing tag to translation.

* Disable debug output for reducing console spam.

* Return correct content type

* Small italian language change

* Added support for country code

* Fixed missing translation for uninstall popup.

* Fix class name shenanigans for  toast notification

* Update dependencies

* Fixed github workflow to include the new locales folder

* Update dependencies to latest version (unless it's React) and fixed the new small errors that cropped up

* Missed a file name change

* Updated dev dependencies to latest version

* Missed a few dev dependencies

* Revert "Update dependencies to latest version (unless it's React) and fixed the new small errors that cropped up"

Messed up merge with a different main branch

* Messed up deletion of rollup config.

* Fix broken pnpm lock file

* Missed a localized string during the merge

* Fixed a parameter mistake in the uninstall text parameter

* Fix pnpm random issues

* Small italian language tweaks

* Fix wrong parameter passed to the uninstall function call

* Another fix on a wrong function parameter

* Additional translation text on the store and branch selection channels

* Changed the default type passed to map to being able to index the two arrays.

* Reverted and reworked the last changes

* Distinguish events in UI for installing vs reinstalling plugins

* Additional fixes for reinstall prompt

* Revert the use of intevalPlural since the parser doesn't seem to support that.

* Missed a routing path in the backend

* Small bugfixes

* Small fixes

* Correctly adding the parameter to the request headers.

* Refactoring of the UI popup modal

* Fix pnpm shenanigans

* Final fixes for the install UI localization

* Clean up unnedeed backend code

* Small rework on text selection.

* Cleaned up parser configuration

* Removed extracttext dependency to pnpmsetup

* Merged translation and cleaned up parser

* Fixed JSON structure after manual merge.

* Added translation to the file picker

* Revert changes to PluginInstallModal

* Reworked the text modal for the final time

* Missed the proper linted text

* Missed the backend change

* Final branch cleanup

* Fixed small translation bleeding

Caused from the manual merge of _old.json files.

* fix extra space in browser.py

* fix extra newline in plugin-loader.tsx

* Cleanup i18next-parser.config.mjs

* Update plugin-loader.tsx

* Cleanup language files

* Better labeling of text

* Fixed language typos in BranchSelect

* Fixed language typos in StoreSelect

* Cleanup plugin-loader.tsx from unused imports

* Removed the path bypass since I'm using authentication from the frontend.

* Reimplemented this component as a functional component.

* Updated dependencies and lockfile

* Removed static route from main.py

Already handled in loader.py

* Small italian coherency fixes

* Fix small typography fixes on plugin name uninstall

* Fixed italian typo on removal popup

* Reenabled manual escaping value in i18next

* Set to fallback to the default language if the string in the JSON file is empty.

* Fixed pnpm wankery

* Added a missed italian text translation string

---------

Co-authored-by: AAGaming <aa@mail.catvibers.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

7 participants