[Feature] Implement internazionalization for Decky Loader (#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 8e8231950f.

* 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 3a39f36f21.

* 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>
This commit is contained in:
Marco Rodolfi
2023-05-02 17:42:39 +02:00
committed by GitHub
parent caf37d681f
commit 35e7c80835
30 changed files with 2289 additions and 339 deletions
@@ -1,5 +1,6 @@
import { Dropdown, Field } from 'decky-frontend-lib';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import Logger from '../../../../logger';
import { callUpdaterMethod } from '../../../../updater';
@@ -14,17 +15,23 @@ enum UpdateBranch {
}
const BranchSelect: FunctionComponent<{}> = () => {
const { t } = useTranslation();
const tBranches = [
t('BranchSelect.update_channel.stable'),
t('BranchSelect.update_channel.prerelease'),
t('BranchSelect.update_channel.testing'),
];
const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Prerelease);
return (
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22)
// 0 being stable, 1 being pre-release and 2 being nightly
<Field label="Decky Update Channel" childrenContainerWidth={'fixed'}>
<Field label={t('BranchSelect.update_channel.label')} childrenContainerWidth={'fixed'}>
<Dropdown
rgOptions={Object.values(UpdateBranch)
.filter((branch) => typeof branch == 'string')
.map((branch) => ({
label: branch,
label: tBranches[UpdateBranch[branch]],
data: UpdateBranch[branch],
}))}
selectedOption={selectedBranch}
@@ -1,19 +1,17 @@
import { Field, Toggle } from 'decky-frontend-lib';
import { useTranslation } from 'react-i18next';
import { FaChrome } from 'react-icons/fa';
import { useSetting } from '../../../../utils/hooks/useSetting';
export default function RemoteDebuggingSettings() {
const [allowRemoteDebugging, setAllowRemoteDebugging] = useSetting<boolean>('cef_forward', false);
const { t } = useTranslation();
return (
<Field
label="Allow Remote CEF Debugging"
description={
<span style={{ whiteSpace: 'pre-line' }}>
Allows unauthenticated access to the CEF debugger to anyone in your network.
</span>
}
label={t('RemoteDebugging.remote_cef.label')}
description={<span style={{ whiteSpace: 'pre-line' }}>{t('RemoteDebugging.remote_cef.desc')}</span>}
icon={<FaChrome style={{ display: 'block' }} />}
>
<Toggle
@@ -1,5 +1,6 @@
import { Dropdown, Field, TextField } from 'decky-frontend-lib';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import { FaShapes } from 'react-icons/fa';
import Logger from '../../../../logger';
@@ -11,17 +12,23 @@ const logger = new Logger('StoreSelect');
const StoreSelect: FunctionComponent<{}> = () => {
const [selectedStore, setSelectedStore] = useSetting<Store>('store', Store.Default);
const [selectedStoreURL, setSelectedStoreURL] = useSetting<string | null>('store-url', null);
const { t } = useTranslation();
const tStores = [
t('StoreSelect.store_channel.default'),
t('StoreSelect.store_channel.testing'),
t('StoreSelect.store_channel.custom'),
];
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22)
// 0 being Default, 1 being Testing and 2 being Custom
return (
<>
<Field label="Plugin Store Channel" childrenContainerWidth={'fixed'}>
<Field label={t('StoreSelect.store_channel.label')} childrenContainerWidth={'fixed'}>
<Dropdown
rgOptions={Object.values(Store)
.filter((store) => typeof store == 'string')
.map((store) => ({
label: store,
label: tStores[Store[store]],
data: Store[store],
}))}
selectedOption={selectedStore}
@@ -33,11 +40,11 @@ const StoreSelect: FunctionComponent<{}> = () => {
</Field>
{selectedStore == Store.Custom && (
<Field
label="Custom Store"
label={t('StoreSelect.custom_store.label')}
indentLevel={1}
description={
<TextField
label={'URL'}
label={t('StoreSelect.custom_store.url_label')}
value={selectedStoreURL || undefined}
onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
/>
@@ -12,6 +12,7 @@ import {
import { useCallback } from 'react';
import { Suspense, lazy } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaExclamation } from 'react-icons/fa';
import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater';
@@ -23,6 +24,7 @@ const MarkdownRenderer = lazy(() => import('../../../Markdown'));
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
const SP = findSP();
const { t } = useTranslation();
return (
<Focusable onCancelButton={closeModal}>
<FocusRing>
@@ -45,7 +47,7 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
<MarkdownRenderer onDismiss={closeModal}>{versionInfo.all[id].body}</MarkdownRenderer>
</WithSuspense>
) : (
'no patch notes for this version'
t('Updater.no_patch_notes_desc')
)}
</div>
</Focusable>
@@ -58,7 +60,7 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
initialColumn={0}
autoFocus={true}
fnGetColumnWidth={() => SP.innerWidth}
name="Decky Updates"
name={t('Updater.decky_updates') as string}
/>
</FocusRing>
</Focusable>
@@ -72,6 +74,8 @@ export default function UpdaterSettings() {
const [updateProgress, setUpdateProgress] = useState<number>(-1);
const [reloading, setReloading] = useState<boolean>(false);
const { t } = useTranslation();
useEffect(() => {
window.DeckyUpdater = {
updateProgress: (i) => {
@@ -93,14 +97,14 @@ export default function UpdaterSettings() {
return (
<>
<Field
onOptionsActionDescription={versionInfo?.all ? 'Patch Notes' : undefined}
onOptionsActionDescription={versionInfo?.all ? t('Updater.patch_notes_desc') : undefined}
onOptionsButton={versionInfo?.all ? showPatchNotes : undefined}
label="Decky Updates"
label={t('Updater.updates.label')}
description={
checkingForUpdates || versionInfo?.remote?.tag_name != versionInfo?.current || !versionInfo?.remote ? (
''
) : (
<span>Up to date: running {versionInfo?.current}</span>
<span>{t('Updater.updates.lat_version', { ver: versionInfo?.current })} </span>
)
}
icon={
@@ -129,10 +133,10 @@ export default function UpdaterSettings() {
}
>
{checkingForUpdates
? 'Checking'
? t('Updater.updates.checking')
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
? 'Check For Updates'
: 'Install Update'}
? t('Updater.updates.check_button')
: t('Updater.updates.install_button')}
</DialogButton>
) : (
<ProgressBarWithInfo
@@ -140,7 +144,7 @@ export default function UpdaterSettings() {
bottomSeparator="none"
nProgress={updateProgress}
indeterminate={reloading}
sOperationText={reloading ? 'Reloading' : 'Updating'}
sOperationText={reloading ? t('Updater.updates.reloading') : t('Updater.updates.updating')}
/>
)}
</Field>
@@ -1,4 +1,5 @@
import { DialogBody, DialogControlsSection, DialogControlsSectionHeader, Field, Toggle } from 'decky-frontend-lib';
import { useTranslation } from 'react-i18next';
import { useDeckyState } from '../../../DeckyState';
import BranchSelect from './BranchSelect';
@@ -13,21 +14,22 @@ export default function GeneralSettings({
setIsDeveloper: (val: boolean) => void;
}) {
const { versionInfo } = useDeckyState();
const { t } = useTranslation();
return (
<DialogBody>
<DialogControlsSection>
<DialogControlsSectionHeader>Updates</DialogControlsSectionHeader>
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.updates.header')}</DialogControlsSectionHeader>
<UpdaterSettings />
</DialogControlsSection>
<DialogControlsSection>
<DialogControlsSectionHeader>Beta Participation</DialogControlsSectionHeader>
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.beta.header')}</DialogControlsSectionHeader>
<BranchSelect />
<StoreSelect />
</DialogControlsSection>
<DialogControlsSection>
<DialogControlsSectionHeader>Other</DialogControlsSectionHeader>
<Field label="Enable Developer Mode">
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.other.header')}</DialogControlsSectionHeader>
<Field label={t('SettingsGeneralIndex.developer_mode.label')}>
<Toggle
value={isDeveloper}
onChange={(toggleValue) => {
@@ -37,8 +39,8 @@ export default function GeneralSettings({
</Field>
</DialogControlsSection>
<DialogControlsSection>
<DialogControlsSectionHeader>About</DialogControlsSectionHeader>
<Field label="Decky Version" focusable={true}>
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.about.header')}</DialogControlsSectionHeader>
<Field label={t('SettingsGeneralIndex.about.decky_version')} focusable={true}>
<div style={{ color: 'var(--gpSystemLighterGrey)' }}>{versionInfo?.current}</div>
</Field>
</DialogControlsSection>