summaryrefslogtreecommitdiff
path: root/frontend/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/modals/PluginInstallModal.tsx39
-rw-r--r--frontend/src/components/modals/TPluginInstallModal.tsx95
-rw-r--r--frontend/src/components/modals/filepicker/index.tsx4
-rw-r--r--frontend/src/components/settings/index.tsx8
-rw-r--r--frontend/src/components/settings/pages/developer/index.tsx52
-rw-r--r--frontend/src/components/settings/pages/general/BranchSelect.tsx11
-rw-r--r--frontend/src/components/settings/pages/general/RemoteDebugging.tsx10
-rw-r--r--frontend/src/components/settings/pages/general/StoreSelect.tsx15
-rw-r--r--frontend/src/components/settings/pages/general/Updater.tsx22
-rw-r--r--frontend/src/components/settings/pages/general/index.tsx14
-rw-r--r--frontend/src/components/settings/pages/plugin_list/index.tsx35
-rw-r--r--frontend/src/components/store/PluginCard.tsx16
-rw-r--r--frontend/src/components/store/Store.tsx54
13 files changed, 281 insertions, 94 deletions
diff --git a/frontend/src/components/modals/PluginInstallModal.tsx b/frontend/src/components/modals/PluginInstallModal.tsx
index 7f0683ee..0e8e3d47 100644
--- a/frontend/src/components/modals/PluginInstallModal.tsx
+++ b/frontend/src/components/modals/PluginInstallModal.tsx
@@ -1,18 +1,31 @@
import { ConfirmModal, Navigation, QuickAccessTab } from 'decky-frontend-lib';
import { FC, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import TPluginInstallModal, { TranslatedPart } from './TPluginInstallModal';
interface PluginInstallModalProps {
artifact: string;
version: string;
hash: string;
- // reinstall: boolean;
+ installType: number;
onOK(): void;
onCancel(): void;
closeModal?(): void;
}
-const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, hash, onOK, onCancel, closeModal }) => {
+const PluginInstallModal: FC<PluginInstallModalProps> = ({
+ artifact,
+ version,
+ hash,
+ installType,
+ onOK,
+ onCancel,
+ closeModal,
+}) => {
const [loading, setLoading] = useState<boolean>(false);
+ const { t } = useTranslation();
+
return (
<ConfirmModal
bOKDisabled={loading}
@@ -26,14 +39,22 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
onCancel={async () => {
await onCancel();
}}
- strTitle={`Install ${artifact}`}
- strOKButtonText={loading ? 'Installing' : 'Install'}
+ strTitle={<TPluginInstallModal trans_part={TranslatedPart.TITLE} trans_type={installType} artifact={artifact} />}
+ strOKButtonText={
+ loading ? (
+ <TPluginInstallModal trans_part={TranslatedPart.BUTTON_PROC} trans_type={installType} />
+ ) : (
+ <TPluginInstallModal trans_part={TranslatedPart.BUTTON_IDLE} trans_type={installType} />
+ )
+ }
>
- Are you sure you want to install {artifact}
- {version ? ` ${version}` : ''}?
- {hash == 'False' && (
- <span style={{ color: 'red' }}> This plugin does not have a hash, you are installing it at your own risk.</span>
- )}
+ <TPluginInstallModal
+ trans_part={TranslatedPart.DESC}
+ trans_type={installType}
+ artifact={artifact}
+ version={version ? version : ''}
+ />
+ {hash == 'False' && <span style={{ color: 'red' }}>{t('PluginInstallModal.no_hash')}</span>}
</ConfirmModal>
);
};
diff --git a/frontend/src/components/modals/TPluginInstallModal.tsx b/frontend/src/components/modals/TPluginInstallModal.tsx
new file mode 100644
index 00000000..3866560e
--- /dev/null
+++ b/frontend/src/components/modals/TPluginInstallModal.tsx
@@ -0,0 +1,95 @@
+import { FC } from 'react';
+import { Translation } from 'react-i18next';
+
+import { InstallType } from '../../plugin';
+
+export enum TranslatedPart {
+ TITLE,
+ DESC,
+ BUTTON_IDLE,
+ BUTTON_PROC,
+}
+interface TPluginInstallModalProps {
+ trans_part: TranslatedPart;
+ trans_type: number;
+ artifact?: string;
+ version?: string;
+}
+
+const TPluginInstallModal: FC<TPluginInstallModalProps> = ({ trans_part, trans_type, artifact, version }) => {
+ return (
+ <Translation>
+ {(t, {}) => {
+ switch (trans_part) {
+ case TranslatedPart.TITLE:
+ switch (trans_type) {
+ case InstallType.INSTALL:
+ return <div>{t('PluginInstallModal.install.title', { artifact: artifact })}</div>;
+ case InstallType.REINSTALL:
+ return <div>{t('PluginInstallModal.reinstall.title', { artifact: artifact })}</div>;
+ case InstallType.UPDATE:
+ return <div>{t('PluginInstallModal.update.title', { artifact: artifact })}</div>;
+ default:
+ return null;
+ }
+ case TranslatedPart.DESC:
+ switch (trans_type) {
+ case InstallType.INSTALL:
+ return (
+ <div>
+ {t('PluginInstallModal.install.desc', {
+ artifact: artifact,
+ version: version,
+ })}
+ </div>
+ );
+ case InstallType.REINSTALL:
+ return (
+ <div>
+ {t('PluginInstallModal.reinstall.desc', {
+ artifact: artifact,
+ version: version,
+ })}
+ </div>
+ );
+ case InstallType.UPDATE:
+ return (
+ <div>
+ {t('PluginInstallModal.update.desc', {
+ artifact: artifact,
+ version: version,
+ })}
+ </div>
+ );
+ default:
+ return null;
+ }
+ case TranslatedPart.BUTTON_IDLE:
+ switch (trans_type) {
+ case InstallType.INSTALL:
+ return <div>{t('PluginInstallModal.install.button_idle')}</div>;
+ case InstallType.REINSTALL:
+ return <div>{t('PluginInstallModal.reinstall.button_idle')}</div>;
+ case InstallType.UPDATE:
+ return <div>{t('PluginInstallModal.update.button_idle')}</div>;
+ default:
+ return null;
+ }
+ case TranslatedPart.BUTTON_PROC:
+ switch (trans_type) {
+ case InstallType.INSTALL:
+ return <div>{t('PluginInstallModal.install.button_processing')}</div>;
+ case InstallType.REINSTALL:
+ return <div>{t('PluginInstallModal.reinstall.button_processing')}</div>;
+ case InstallType.UPDATE:
+ return <div>{t('PluginInstallModal.update.button_processing')}</div>;
+ default:
+ return null;
+ }
+ }
+ }}
+ </Translation>
+ );
+};
+
+export default TPluginInstallModal;
diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx
index ec3fc260..629f4ec5 100644
--- a/frontend/src/components/modals/filepicker/index.tsx
+++ b/frontend/src/components/modals/filepicker/index.tsx
@@ -2,6 +2,7 @@ import { DialogButton, Focusable, SteamSpinner, TextField } from 'decky-frontend
import { useEffect } from 'react';
import { FunctionComponent, useState } from 'react';
import { FileIcon, defaultStyles } from 'react-file-icon';
+import { useTranslation } from 'react-i18next';
import { FaArrowUp, FaFolder } from 'react-icons/fa';
import Logger from '../../../logger';
@@ -47,6 +48,7 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
onSubmit,
closeModal,
}) => {
+ const { t } = useTranslation();
if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path
const [path, setPath] = useState<string>(startPath);
const [listing, setListing] = useState<FileListing>({ files: [], realpath: path });
@@ -158,7 +160,7 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
closeModal?.();
}}
>
- Use this folder
+ {t('FilePickerIndex.folder.select')}
</DialogButton>
)}
</div>
diff --git a/frontend/src/components/settings/index.tsx b/frontend/src/components/settings/index.tsx
index 6f104710..f3a76407 100644
--- a/frontend/src/components/settings/index.tsx
+++ b/frontend/src/components/settings/index.tsx
@@ -1,5 +1,6 @@
import { SidebarNavigation } from 'decky-frontend-lib';
import { lazy } from 'react';
+import { useTranslation } from 'react-i18next';
import { FaCode, FaPlug } from 'react-icons/fa';
import { useSetting } from '../../utils/hooks/useSetting';
@@ -12,22 +13,23 @@ const DeveloperSettings = lazy(() => import('./pages/developer'));
export default function SettingsPage() {
const [isDeveloper, setIsDeveloper] = useSetting<boolean>('developer.enabled', false);
+ const { t } = useTranslation();
const pages = [
{
- title: 'Decky',
+ title: t('SettingsIndex.general_title'),
content: <GeneralSettings isDeveloper={isDeveloper} setIsDeveloper={setIsDeveloper} />,
route: '/decky/settings/general',
icon: <DeckyIcon />,
},
{
- title: 'Plugins',
+ title: t('SettingsIndex.plugins_title'),
content: <PluginList />,
route: '/decky/settings/plugins',
icon: <FaPlug />,
},
{
- title: 'Developer',
+ title: t('SettingsIndex.developer_title'),
content: (
<WithSuspense>
<DeveloperSettings />
diff --git a/frontend/src/components/settings/pages/developer/index.tsx b/frontend/src/components/settings/pages/developer/index.tsx
index e6e37813..7a62c052 100644
--- a/frontend/src/components/settings/pages/developer/index.tsx
+++ b/frontend/src/components/settings/pages/developer/index.tsx
@@ -8,6 +8,7 @@ import {
Toggle,
} from 'decky-frontend-lib';
import { useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { FaFileArchive, FaLink, FaReact, FaSteamSymbol } from 'react-icons/fa';
import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer';
@@ -24,8 +25,10 @@ const installFromZip = () => {
installFromURL(url);
} else {
window.DeckyPluginLoader.toaster.toast({
+ //title: t('SettingsDeveloperIndex.toast_zip.title'),
title: 'Decky',
- body: `Installation failed! Only ZIP files are supported.`,
+ //body: t('SettingsDeveloperIndex.toast_zip.body'),
+ body: 'Installation failed! Only ZIP files are supported.',
onClick: installFromZip,
});
}
@@ -38,33 +41,47 @@ export default function DeveloperSettings() {
const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', '');
const [pluginURL, setPluginURL] = useState('');
const textRef = useRef<HTMLDivElement>(null);
+ const { t } = useTranslation();
return (
<DialogBody>
<DialogControlsSection>
- <DialogControlsSectionHeader>Third-Party Plugins</DialogControlsSectionHeader>
- <Field label="Install Plugin from ZIP File" icon={<FaFileArchive style={{ display: 'block' }} />}>
- <DialogButton onClick={installFromZip}>Browse</DialogButton>
+ <DialogControlsSectionHeader>
+ {t('SettingsDeveloperIndex.third_party_plugins.header')}
+ </DialogControlsSectionHeader>
+ <Field
+ label={t('SettingsDeveloperIndex.third_party_plugins.label_zip')}
+ icon={<FaFileArchive style={{ display: 'block' }} />}
+ >
+ <DialogButton onClick={installFromZip}>
+ {t('SettingsDeveloperIndex.third_party_plugins.button_zip')}
+ </DialogButton>
</Field>
<Field
- label="Install Plugin from URL"
- description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />}
+ label={t('SettingsDeveloperIndex.third_party_plugins.label_url')}
+ description={
+ <TextField
+ label={t('SettingsDeveloperIndex.third_party_plugins.label_desc')}
+ value={pluginURL}
+ onChange={(e) => setPluginURL(e?.target.value)}
+ />
+ }
icon={<FaLink style={{ display: 'block' }} />}
>
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
- Install
+ {t('SettingsDeveloperIndex.third_party_plugins.button_install')}
</DialogButton>
</Field>
</DialogControlsSection>
<DialogControlsSection>
- <DialogControlsSectionHeader>Other</DialogControlsSectionHeader>
+ <DialogControlsSectionHeader>{t('SettingsDeveloperIndex.header_other')}</DialogControlsSectionHeader>
<RemoteDebuggingSettings />
<Field
- label="Enable Valve Internal"
+ label={t('SettingsDeveloperIndex.valve_internal.label')}
description={
<span style={{ whiteSpace: 'pre-line' }}>
- Enables the Valve internal developer menu.{' '}
- <span style={{ color: 'red' }}>Do not touch anything in this menu unless you know what it does.</span>
+ {t('SettingsDeveloperIndex.valve_internal.desc1')}{' '}
+ <span style={{ color: 'red' }}>{t('SettingsDeveloperIndex.valve_internal.desc2')}</span>
</span>
}
icon={<FaSteamSymbol style={{ display: 'block' }} />}
@@ -78,17 +95,18 @@ export default function DeveloperSettings() {
/>
</Field>
<Field
- label="Enable React DevTools"
+ label={t('SettingsDeveloperIndex.react_devtools.label')}
description={
<>
- <span style={{ whiteSpace: 'pre-line' }}>
- Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set
- the IP address before enabling.
- </span>
+ <span style={{ whiteSpace: 'pre-line' }}>{t('SettingsDeveloperIndex.react_devtools.desc')}</span>
<br />
<br />
<div ref={textRef}>
- <TextField label={'IP'} value={reactDevtoolsIP} onChange={(e) => setReactDevtoolsIP(e?.target.value)} />
+ <TextField
+ label={t('SettingsDeveloperIndex.react_devtools.ip_label')}
+ value={reactDevtoolsIP}
+ onChange={(e) => setReactDevtoolsIP(e?.target.value)}
+ />
</div>
</>
}
diff --git a/frontend/src/components/settings/pages/general/BranchSelect.tsx b/frontend/src/components/settings/pages/general/BranchSelect.tsx
index 5387b655..d966ff62 100644
--- a/frontend/src/components/settings/pages/general/BranchSelect.tsx
+++ b/frontend/src/components/settings/pages/general/BranchSelect.tsx
@@ -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}
diff --git a/frontend/src/components/settings/pages/general/RemoteDebugging.tsx b/frontend/src/components/settings/pages/general/RemoteDebugging.tsx
index db604c69..60d57d91 100644
--- a/frontend/src/components/settings/pages/general/RemoteDebugging.tsx
+++ b/frontend/src/components/settings/pages/general/RemoteDebugging.tsx
@@ -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
diff --git a/frontend/src/components/settings/pages/general/StoreSelect.tsx b/frontend/src/components/settings/pages/general/StoreSelect.tsx
index 40e70301..ebf1bd81 100644
--- a/frontend/src/components/settings/pages/general/StoreSelect.tsx
+++ b/frontend/src/components/settings/pages/general/StoreSelect.tsx
@@ -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)}
/>
diff --git a/frontend/src/components/settings/pages/general/Updater.tsx b/frontend/src/components/settings/pages/general/Updater.tsx
index 1ee31e6c..927a99b0 100644
--- a/frontend/src/components/settings/pages/general/Updater.tsx
+++ b/frontend/src/components/settings/pages/general/Updater.tsx
@@ -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>
diff --git a/frontend/src/components/settings/pages/general/index.tsx b/frontend/src/components/settings/pages/general/index.tsx
index 97fd3e42..96ae6782 100644
--- a/frontend/src/components/settings/pages/general/index.tsx
+++ b/frontend/src/components/settings/pages/general/index.tsx
@@ -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>
diff --git a/frontend/src/components/settings/pages/plugin_list/index.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx
index ac954601..d7ff7bd9 100644
--- a/frontend/src/components/settings/pages/plugin_list/index.tsx
+++ b/frontend/src/components/settings/pages/plugin_list/index.tsx
@@ -10,8 +10,10 @@ import {
showContextMenu,
} from 'decky-frontend-lib';
import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { FaDownload, FaEllipsisH, FaRecycle } from 'react-icons/fa';
+import { InstallType } from '../../../../plugin';
import { StorePluginVersion, getPluginList, requestPluginInstall } from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting';
import { useDeckyState } from '../../../DeckyState';
@@ -25,19 +27,33 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) {
const remotePlugin = serverData?.find((x) => x.name == pluginName);
if (remotePlugin && remotePlugin.versions?.length > 0) {
const currentVersionData = remotePlugin.versions.find((version) => version.name == currentVersion);
- if (currentVersionData) requestPluginInstall(pluginName, currentVersionData);
+ if (currentVersionData) requestPluginInstall(pluginName, currentVersionData, InstallType.REINSTALL);
}
}
function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
const data = props.entry.data;
+ const { t } = useTranslation();
let pluginName = labelToName(props.entry.label, data?.version);
const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
showContextMenu(
- <Menu label="Plugin Actions">
- <MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(pluginName, data?.version)}>Reload</MenuItem>
- <MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(pluginName)}>Uninstall</MenuItem>
+ <Menu label={t('PluginListIndex.plugin_actions')}>
+ <MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(pluginName, data?.version)}>
+ {t('PluginListIndex.reload')}
+ </MenuItem>
+ <MenuItem
+ onSelected={() =>
+ window.DeckyPluginLoader.uninstallPlugin(
+ pluginName,
+ t('PluginLoader.plugin_uninstall.title', { name: pluginName }),
+ t('PluginLoader.plugin_uninstall.button'),
+ t('PluginLoader.plugin_uninstall.desc', { name: pluginName }),
+ )
+ }
+ >
+ {t('PluginListIndex.uninstall')}
+ </MenuItem>
</Menu>,
e.currentTarget ?? window,
);
@@ -48,11 +64,11 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
{data?.update ? (
<DialogButton
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
- onClick={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion)}
- onOKButton={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion)}
+ onClick={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)}
+ onOKButton={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)}
>
<div style={{ display: 'flex', flexDirection: 'row' }}>
- Update to {data?.update?.name}
+ {t('PluginListIndex.update_to', { name: data?.update?.name })}
<FaDownload style={{ paddingLeft: '2rem' }} />
</div>
</DialogButton>
@@ -63,7 +79,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
onOKButton={() => reinstallPlugin(pluginName, data?.version)}
>
<div style={{ display: 'flex', flexDirection: 'row' }}>
- Reinstall
+ {t('PluginListIndex.reinstall')}
<FaRecycle style={{ paddingLeft: '5.3rem' }} />
</div>
</DialogButton>
@@ -90,6 +106,7 @@ export default function PluginList() {
'pluginOrder',
plugins.map((plugin) => plugin.name),
);
+ const { t } = useTranslation();
useEffect(() => {
window.DeckyPluginLoader.checkPluginUpdates();
@@ -115,7 +132,7 @@ export default function PluginList() {
if (plugins.length === 0) {
return (
<div>
- <p>No plugins installed</p>
+ <p>{t('PluginListIndex.no_plugin')}</p>
</div>
);
}
diff --git a/frontend/src/components/store/PluginCard.tsx b/frontend/src/components/store/PluginCard.tsx
index 828d3ae9..b8c622db 100644
--- a/frontend/src/components/store/PluginCard.tsx
+++ b/frontend/src/components/store/PluginCard.tsx
@@ -7,7 +7,9 @@ import {
SuspensefulImage,
} from 'decky-frontend-lib';
import { FC, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { InstallType } from '../../plugin';
import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store';
interface PluginCardProps {
@@ -18,6 +20,8 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
const [selectedOption, setSelectedOption] = useState<number>(0);
const root: boolean = plugin.tags.some((tag) => tag === 'root');
+ const { t } = useTranslation();
+
return (
<div
className="deckyStoreCard"
@@ -97,7 +101,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
plugin.description
) : (
<span>
- <i style={{ color: '#666' }}>No description provided.</i>
+ <i style={{ color: '#666' }}>{t('PluginCard.plugin_no_desc')}</i>
</span>
)}
</span>
@@ -109,7 +113,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
color: '#fee75c',
}}
>
- <i>This plugin has full access to your Steam Deck.</i>{' '}
+ <i>{t('PluginCard.plugin_full_access')}</i>{' '}
<a
className="deckyStoreCardDescriptionRootLink"
href="https://deckbrew.xyz/root"
@@ -144,9 +148,11 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
<ButtonItem
bottomSeparator="none"
layout="below"
- onClick={() => requestPluginInstall(plugin.name, plugin.versions[selectedOption])}
+ onClick={() =>
+ requestPluginInstall(plugin.name, plugin.versions[selectedOption], InstallType.INSTALL)
+ }
>
- <span className="deckyStoreCardInstallText">Install</span>
+ <span className="deckyStoreCardInstallText">{t('PluginCard.plugin_install')}</span>
</ButtonItem>
</div>
<div
@@ -163,7 +169,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
label: version.name,
})) as SingleDropdownOption[]
}
- menuLabel="Plugin Version"
+ menuLabel={t('PluginCard.plugin_version_label') as string}
selectedOption={selectedOption}
onChange={({ data }) => setSelectedOption(data)}
/>
diff --git a/frontend/src/components/store/Store.tsx b/frontend/src/components/store/Store.tsx
index 68f6c077..f2d941cd 100644
--- a/frontend/src/components/store/Store.tsx
+++ b/frontend/src/components/store/Store.tsx
@@ -9,6 +9,7 @@ import {
findModule,
} from 'decky-frontend-lib';
import { FC, useEffect, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import logo from '../../../assets/plugin_store.png';
import Logger from '../../logger';
@@ -25,6 +26,8 @@ const StorePage: FC<{}> = () => {
return false;
});
+ const { t } = useTranslation();
+
useEffect(() => {
(async () => {
const res = await getPluginList();
@@ -54,13 +57,13 @@ const StorePage: FC<{}> = () => {
}}
tabs={[
{
- title: 'Browse',
+ title: t('Store.store_tabs.title'),
content: <BrowseTab children={{ data: data }} />,
id: 'browse',
renderTabAddon: () => <span className={TabCount}>{data.length}</span>,
},
{
- title: 'About',
+ title: t('Store.store_tabs.about'),
content: <AboutTab />,
id: 'about',
},
@@ -73,10 +76,12 @@ const StorePage: FC<{}> = () => {
};
const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
+ const { t } = useTranslation();
+
const sortOptions = useMemo(
(): DropdownOption[] => [
- { data: 1, label: 'Alphabetical (A to Z)' },
- { data: 2, label: 'Alphabetical (Z to A)' },
+ { data: 1, label: t('Store.store_tabs.alph_desc') },
+ { data: 2, label: t('Store.store_tabs.alph_asce') },
],
[],
);
@@ -105,11 +110,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
width: '47.5%',
}}
>
- <span className="DialogLabel">Sort</span>
+ <span className="DialogLabel">{t("Store.store_sort.label")}</span>
<Dropdown
- menuLabel="Sort"
+ menuLabel={t("Store.store_sort.label") as string}
rgOptions={sortOptions}
- strDefaultLabel="Last Updated (Newest)"
+ strDefaultLabel={t("Store.store_sort.label_def") as string}
selectedOption={selectedSort}
onChange={(e) => setSort(e.data)}
/>
@@ -122,11 +127,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
marginLeft: 'auto',
}}
>
- <span className="DialogLabel">Filter</span>
+ <span className="DialogLabel">{t("Store.store_filter.label")}</span>
<Dropdown
- menuLabel="Filter"
+ menuLabel={t("Store.store_filter.label")}
rgOptions={filterOptions}
- strDefaultLabel="All"
+ strDefaultLabel={t("Store.store_filter.label_def")}
selectedOption={selectedFilter}
onChange={(e) => setFilter(e.data)}
/>
@@ -136,7 +141,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
<div style={{ justifyContent: 'center', display: 'flex' }}>
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
<div style={{ width: '100%' }}>
- <TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
+ <TextField label={t("Store.store_search.label")} value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
</div>
</Focusable>
</div>
@@ -151,11 +156,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
maxWidth: '100%',
}}
>
- <span className="DialogLabel">Sort</span>
+ <span className="DialogLabel">{t('Store.store_sort.label')}</span>
<Dropdown
- menuLabel="Sort"
+ menuLabel={t('Store.store_sort.label') as string}
rgOptions={sortOptions}
- strDefaultLabel="Last Updated (Newest)"
+ strDefaultLabel={t('Store.store_sort.label_def') as string}
selectedOption={selectedSort}
onChange={(e) => setSort(e.data)}
/>
@@ -165,7 +170,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
<div style={{ justifyContent: 'center', display: 'flex' }}>
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
<div style={{ width: '100%' }}>
- <TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
+ <TextField
+ label={t('Store.store_search.label')}
+ value={searchFieldValue}
+ onChange={(e) => setSearchValue(e.target.value)}
+ />
</div>
</Focusable>
</div>
@@ -192,6 +201,8 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
};
const AboutTab: FC<{}> = () => {
+ const { t } = useTranslation();
+
return (
<div
style={{
@@ -216,7 +227,7 @@ const AboutTab: FC<{}> = () => {
/>
<span className="deckyStoreAboutHeader">Testing</span>
<span>
- Please consider testing new plugins to help the Decky Loader team!{' '}
+ {t('Store.store_testing_cta')}{' '}
<a
href="https://deckbrew.xyz/testing"
target="_blank"
@@ -227,13 +238,10 @@ const AboutTab: FC<{}> = () => {
deckbrew.xyz/testing
</a>
</span>
- <span className="deckyStoreAboutHeader">Contributing</span>
- <span>
- If you would like to contribute to the Decky Plugin Store, check the SteamDeckHomebrew/decky-plugin-template
- repository on GitHub. Information on development and distribution is available in the README.
- </span>
- <span className="deckyStoreAboutHeader">Source Code</span>
- <span>All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.</span>
+ <span className="deckyStoreAboutHeader">{t('Store.store_contrib.label')}</span>
+ <span>{t('Store.store_contrib.desc')}</span>
+ <span className="deckyStoreAboutHeader">{t('Store.store_source.label')}</span>
+ <span>{t('Store.store_source.desc')}</span>
</div>
);
};