diff options
Diffstat (limited to 'frontend/src/components')
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> ); }; |
