First iteration for internationalization of the loader

This commit is contained in:
Marco Rodolfi
2023-01-13 10:45:24 +01:00
parent 16681fabb5
commit 2ebcc67bb5
26 changed files with 230 additions and 43 deletions
+3
View File
@@ -42,7 +42,10 @@
},
"dependencies": {
"decky-frontend-lib": "^3.7.14",
"i18next": "^22.0.6",
"i18next-fs-backend": "^2.0.0",
"react-file-icon": "^1.2.0",
"react-i18next": "^12.0.0",
"react-icons": "^4.4.0",
"react-markdown": "^8.0.3",
"remark-gfm": "^3.0.1"
+58
View File
@@ -12,6 +12,8 @@ specifiers:
'@types/webpack': ^5.28.0
decky-frontend-lib: ^3.7.14
husky: ^8.0.1
i18next: ^22.0.6
i18next-fs-backend: ^2.0.0
import-sort-style-module: ^6.0.0
inquirer: ^8.2.4
prettier: ^2.7.1
@@ -19,6 +21,7 @@ specifiers:
react: 16.14.0
react-dom: 16.14.0
react-file-icon: ^1.2.0
react-i18next: ^12.0.0
react-icons: ^4.4.0
react-markdown: ^8.0.3
remark-gfm: ^3.0.1
@@ -31,7 +34,10 @@ specifiers:
dependencies:
decky-frontend-lib: 3.7.14
i18next: 22.0.6
i18next-fs-backend: 2.0.0
react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty
react-i18next: 12.0.0_l5i64r2igudm3ypjcze5pllh6e
react-icons: 4.4.0_react@16.14.0
react-markdown: 8.0.3_vshvapmxg47tngu7tvrsqpq55u
remark-gfm: 3.0.1
@@ -222,6 +228,13 @@ packages:
'@babel/types': 7.18.8
dev: true
/@babel/runtime/7.20.1:
resolution: {integrity: sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
dev: false
/@babel/template/7.18.6:
resolution: {integrity: sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==}
engines: {node: '>=6.9.0'}
@@ -1233,12 +1246,28 @@ packages:
resolution: {integrity: sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==}
dev: false
/html-parse-stringify/3.0.1:
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
dependencies:
void-elements: 3.1.0
dev: false
/husky/8.0.1:
resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==}
engines: {node: '>=14'}
hasBin: true
dev: true
/i18next-fs-backend/2.0.0:
resolution: {integrity: sha512-zlwzcoUKlveoRt/SxgeP8i/6p1rxwxZ+x0w4sfCTY1zgUlhhnoxBSRX3GjVIsDJm5mky8Hpr//UX93UIknK7yQ==}
dev: false
/i18next/22.0.6:
resolution: {integrity: sha512-RlreNGoPIdDP4QG+qSA9PxZKGwlzmcozbI9ObI6+OyUa/Rp0EjZZA9ubyBjw887zVNZsC+7FI3sXX8oiTzAfig==}
dependencies:
'@babel/runtime': 7.20.1
dev: false
/iconv-lite/0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -2107,6 +2136,26 @@ packages:
tinycolor2: 1.4.2
dev: false
/react-i18next/12.0.0_l5i64r2igudm3ypjcze5pllh6e:
resolution: {integrity: sha512-/O7N6aIEAl1FaWZBNvhdIo9itvF/MO/nRKr9pYqRc9LhuC1u21SlfwpiYQqvaeNSEW3g3qUXLREOWMt+gxrWbg==}
peerDependencies:
i18next: '>= 19.0.0'
react: '>= 16.8.0'
react-dom: '*'
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
dependencies:
'@babel/runtime': 7.20.1
html-parse-stringify: 3.0.1
i18next: 22.0.6
react: 16.14.0
react-dom: 16.14.0_react@16.14.0
dev: false
/react-icons/4.4.0_react@16.14.0:
resolution: {integrity: sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==}
peerDependencies:
@@ -2166,6 +2215,10 @@ packages:
util-deprecate: 1.0.2
dev: true
/regenerator-runtime/0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: false
/remark-gfm/3.0.1:
resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==}
dependencies:
@@ -2617,6 +2670,11 @@ packages:
vfile-message: 3.1.2
dev: false
/void-elements/3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
dev: false
/watchpack/2.4.0:
resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
engines: {node: '>=10.13.0'}
+7 -4
View File
@@ -1,5 +1,6 @@
import { SidebarNavigation } from 'decky-frontend-lib';
import { lazy } from 'react';
import { useTranslation } from 'react-i18next';
import { useSetting } from '../../utils/hooks/useSetting';
import WithSuspense from '../WithSuspense';
@@ -8,17 +9,19 @@ import PluginList from './pages/plugin_list';
const DeveloperSettings = lazy(() => import('./pages/developer'));
const { t } = useTranslation('SettingsIndex');
export default function SettingsPage() {
const [isDeveloper, setIsDeveloper] = useSetting<boolean>('developer.enabled', false);
const pages = [
{
title: 'General',
title: t('general_title'),
content: <GeneralSettings isDeveloper={isDeveloper} setIsDeveloper={setIsDeveloper} />,
route: '/decky/settings/general',
},
{
title: 'Plugins',
title: t('plugins_title'),
content: <PluginList />,
route: '/decky/settings/plugins',
},
@@ -26,7 +29,7 @@ export default function SettingsPage() {
if (isDeveloper)
pages.push({
title: 'Developer',
title: t('developer_title'),
content: (
<WithSuspense>
<DeveloperSettings />
@@ -35,5 +38,5 @@ export default function SettingsPage() {
route: '/decky/settings/developer',
});
return <SidebarNavigation title="Decky Settings" showTitle pages={pages} />;
return <SidebarNavigation title={t('settings_navbar') as string}showTitle pages={pages} />;
}
@@ -1,5 +1,6 @@
import { Field, Focusable, TextField, Toggle } from 'decky-frontend-lib';
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { FaReact, FaSteamSymbol } from 'react-icons/fa';
import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer';
@@ -10,15 +11,16 @@ export default function DeveloperSettings() {
const [reactDevtoolsEnabled, setReactDevtoolsEnabled] = useSetting<boolean>('developer.rdt.enabled', false);
const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', '');
const textRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation('DeveloperIndex');
return (
<>
<Field
label="Enable Valve Internal"
label={t('label_valve_internal')}
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('valve_internal_desc1')}{' '}
<span style={{ color: 'red' }}>{t('valve_internal_desc2')}</span>
</span>
}
icon={<FaSteamSymbol style={{ display: 'block' }} />}
@@ -55,12 +57,11 @@ export default function DeveloperSettings() {
}
>
<Field
label="Enable React DevTools"
label={t('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.
{t('react_devtools_desc')}
</span>
<div ref={textRef}>
<TextField label={'IP'} value={reactDevtoolsIP} onChange={(e) => setReactDevtoolsIP(e?.target.value)} />
@@ -1,11 +1,13 @@
import { Dropdown, Field } from 'decky-frontend-lib';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import Logger from '../../../../logger';
import { callUpdaterMethod } from '../../../../updater';
import { useSetting } from '../../../../utils/hooks/useSetting';
const logger = new Logger('BranchSelect');
const { t } = useTranslation('BranchSelect');
enum UpdateBranch {
Stable,
@@ -19,7 +21,7 @@ const BranchSelect: FunctionComponent<{}> = () => {
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="Update Channel">
<Field label={t('update_channel_label')}>
<Dropdown
rgOptions={Object.values(UpdateBranch)
.filter((branch) => typeof branch == 'string')
@@ -1,17 +1,19 @@
import { Field, Toggle } from 'decky-frontend-lib';
import { useTranslation } from 'react-i18next';
import { FaBug } 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('RemoteDebugging');
return (
<Field
label="Allow Remote CEF Debugging"
label={t('remote_cef_label')}
description={
<span style={{ whiteSpace: 'pre-line' }}>
Allow unauthenticated access to the CEF debugger to anyone in your network
{t('remote_cef_desc')}
</span>
}
icon={<FaBug style={{ display: 'block' }} />}
@@ -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';
@@ -7,6 +8,7 @@ import { Store } from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting';
const logger = new Logger('StoreSelect');
const { t } = useTranslation('StoreSelect');
const StoreSelect: FunctionComponent<{}> = () => {
const [selectedStore, setSelectedStore] = useSetting<Store>('store', Store.Default);
@@ -16,7 +18,7 @@ const StoreSelect: FunctionComponent<{}> = () => {
// 0 being Default, 1 being Testing and 2 being Custom
return (
<>
<Field label="Store Channel">
<Field label={t('store_channel_label')}>
<Dropdown
rgOptions={Object.values(Store)
.filter((store) => typeof store == 'string')
@@ -33,7 +35,7 @@ const StoreSelect: FunctionComponent<{}> = () => {
</Field>
{selectedStore == Store.Custom && (
<Field
label="Custom Store"
label={t('custom_store_label')}
indentLevel={1}
description={
<TextField
@@ -11,6 +11,7 @@ import {
import { useCallback } from 'react';
import { Suspense, lazy } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaArrowDown } from 'react-icons/fa';
import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater';
@@ -20,6 +21,7 @@ import InlinePatchNotes from '../../../patchnotes/InlinePatchNotes';
import WithSuspense from '../../../WithSuspense';
const MarkdownRenderer = lazy(() => import('../../../Markdown'));
const { t } = useTranslation('Updater');
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
const SP = findSP();
@@ -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("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('decky_updates') as string}
/>
</FocusRing>
</Focusable>
@@ -95,11 +97,11 @@ export default function UpdaterSettings() {
<Field
onOptionsActionDescription={versionInfo?.all ? 'Patch Notes' : undefined}
onOptionsButton={versionInfo?.all ? showPatchNotes : undefined}
label="Updates"
label={t('updates_label')}
description={
versionInfo && (
<span style={{ whiteSpace: 'pre-line' }}>{`Current version: ${versionInfo.current}\n${
versionInfo.updatable ? `Latest version: ${versionInfo.remote?.tag_name}` : ''
<span style={{ whiteSpace: 'pre-line' }}>{`${t('updates_cur_version', versionInfo.current)}\n${
versionInfo.updatable ? t('updates_lat_version', versionInfo.remote?.tag_name) : ''
}`}</span>
)
}
@@ -129,10 +131,10 @@ export default function UpdaterSettings() {
}
>
{checkingForUpdates
? 'Checking'
? t('updates_checking')
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
? 'Check For Updates'
: 'Install Update'}
? t('updates_check_button')
: t('updates_install_button')}
</DialogButton>
) : (
<ProgressBarWithInfo
@@ -140,7 +142,7 @@ export default function UpdaterSettings() {
bottomSeparator="none"
nProgress={updateProgress}
indeterminate={reloading}
sOperationText={reloading ? 'Reloading' : 'Updating'}
sOperationText={reloading ? t("updates_reloading") : t("updates_updating")}
/>
)}
</Field>
@@ -1,5 +1,6 @@
import { DialogButton, Field, TextField, Toggle } from 'decky-frontend-lib';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FaShapes, FaTools } from 'react-icons/fa';
import { installFromURL } from '../../../../store';
@@ -16,6 +17,7 @@ export default function GeneralSettings({
setIsDeveloper: (val: boolean) => void;
}) {
const [pluginURL, setPluginURL] = useState('');
const { t } = useTranslation('SettingsGeneralIndex');
return (
<div>
@@ -24,8 +26,8 @@ export default function GeneralSettings({
<StoreSelect />
<RemoteDebuggingSettings />
<Field
label="Developer mode"
description={<span style={{ whiteSpace: 'pre-line' }}>Enables Decky's developer settings.</span>}
label={t("developer_mode_label")}
description={<span style={{ whiteSpace: 'pre-line' }}>{t("developer_mode_desc")}</span>}
icon={<FaTools style={{ display: 'block' }} />}
>
<Toggle
@@ -36,12 +38,12 @@ export default function GeneralSettings({
/>
</Field>
<Field
label="Manual plugin install"
label={t("manual_plugin_label")}
description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />}
icon={<FaShapes style={{ display: 'block' }} />}
>
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
Install
{t("manual_plugin_install_button")}
</DialogButton>
</Field>
</div>
@@ -1,5 +1,6 @@
import { DialogButton, Focusable, Menu, MenuItem, showContextMenu } from 'decky-frontend-lib';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { FaDownload, FaEllipsisH } from 'react-icons/fa';
import { requestPluginInstall } from '../../../../store';
@@ -7,6 +8,7 @@ import { useDeckyState } from '../../../DeckyState';
export default function PluginList() {
const { plugins, updates } = useDeckyState();
const { t } = useTranslation('PluginListIndex');
useEffect(() => {
window.DeckyPluginLoader.checkPluginUpdates();
@@ -15,7 +17,7 @@ export default function PluginList() {
if (plugins.length === 0) {
return (
<div>
<p>No plugins installed</p>
<p>{t('list_no_plugin')}</p>
</div>
);
}
@@ -36,7 +38,7 @@ export default function PluginList() {
onClick={() => requestPluginInstall(name, update)}
>
<div style={{ display: 'flex', flexDirection: 'row' }}>
Update to {update.name}
{t('list_update_to', update.name)}
<FaDownload style={{ paddingLeft: '2rem' }} />
</div>
</DialogButton>
@@ -45,11 +47,11 @@ export default function PluginList() {
style={{ height: '40px', width: '40px', padding: '10px 12px', minWidth: '40px' }}
onClick={(e: MouseEvent) =>
showContextMenu(
<Menu label="Plugin Actions">
<Menu label={t('list_plug_actions_label')}>
<MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(name, version)}>
Reload
{t('reload')}
</MenuItem>
<MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(name)}>Uninstall</MenuItem>
<MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(name)}>{t('uninstall')}</MenuItem>
</Menu>,
e.currentTarget ?? window,
)
+4 -1
View File
@@ -10,6 +10,7 @@ import {
staticClasses,
} from 'decky-frontend-lib';
import { FC, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store';
@@ -17,6 +18,8 @@ interface PluginCardProps {
plugin: StorePlugin;
}
const { t } = useTranslation('PluginCard');
const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
const [selectedOption, setSelectedOption] = useState<number>(0);
const buttonRef = useRef<HTMLDivElement>(null);
@@ -173,7 +176,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
label: version.name,
})) as SingleDropdownOption[]
}
strDefaultLabel={'Select a version'}
strDefaultLabel={t('select_version') as string}
selectedOption={selectedOption}
onChange={({ data }) => setSelectedOption(data)}
/>
+2
View File
@@ -22,6 +22,8 @@ import { FaReact } from 'react-icons/fa';
import Logger from './logger';
import { getSetting } from './utils/settings';
import "i18n.ts";
const logger = new Logger('DeveloperMode');
let removeSettingsObserver: () => void = () => {};
+32
View File
@@ -0,0 +1,32 @@
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from 'i18next-fs-backend';
i18next
.use(initReactI18next)
.use(Backend)
.init({
backend:
{
// path where resources get loaded from, or a function
// returning a path:
// function(lngs, namespaces) { return customPath; }
// the returned path will interpolate lng, ns if provided like giving a static path
loadPath: '/locales/{{lng}}/{{ns}}.json',
// path to post missing resources
// addPath: '/locales/{{lng}}/{{ns}}.missing.json',
// if you use i18next-fs-backend as caching layer in combination with i18next-chained-backend, you can optionally set an expiration time
// an example on how to use it as cache layer can be found here: https://github.com/i18next/i18next-fs-backend/blob/master/example/caching/app.js
// expirationTime: 60 * 60 * 1000
},
fallbackLng:"en",
fallbackNS:"Common",
lng: "en",
interpolation: {
escapeValue: false,
},
});
export default i18next;
+1
View File
@@ -1,5 +1,6 @@
import PluginLoader from './plugin-loader';
import { DeckyUpdater } from './updater';
import "i18n.ts";
declare global {
interface Window {
@@ -0,0 +1,3 @@
{
"update_channel_label": "Update Channel"
}
+4
View File
@@ -0,0 +1,4 @@
{
"reload": "Reload",
"uninstall": "Uninstall"
}
@@ -0,0 +1,7 @@
{
"valve_internal_label": "Enable Valve Internal",
"valve_internal_desc1": "Enables the Valve internal developer menu.",
"valve_internal_desc2": "Do not touch anything in this menu unless you know what it does.",
"react_devtools_label": "Enable React DevTools",
"react_devtools_desc": "Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set the IP address before enabling."
}
+3
View File
@@ -0,0 +1,3 @@
{
"select_version": "Select a version"
}
@@ -0,0 +1,5 @@
{
"list_no_plugin": "No plugins installed!",
"list_update_to": "Update to {{name}}",
"list_plug_actions_label": "Plugin Actions"
}
@@ -0,0 +1,4 @@
{
"remote_cef_label": "Allow Remote CEF Debugging",
"remote_cef_desc": "Allow unauthenticated access to the CEF debugger to anyone in your network"
}
@@ -0,0 +1,6 @@
{
"developer_mode_label": "Developer mode",
"developer_mode_desc": "Enables Decky's developer settings.",
"manual_plugin_label": "Manual plugin install",
"manual_plugin_install_button": "Install"
}
@@ -0,0 +1,6 @@
{
"general_title": "General",
"plugins_title": "Plugins",
"developer_title": "Developer",
"navbar_settings": "Decky Settings"
}
+4
View File
@@ -0,0 +1,4 @@
{
"store_channel_label": "Store Channel",
"custom_store_label": "Custom Store"
}
+14
View File
@@ -0,0 +1,14 @@
{
"no_patch_notes_desc": "no patch notes for this version",
"decky_updates": "Decky Updates",
"updates_label": "Updates",
"updates_cur_version": "Current version: {{ver}}",
"updates_lat_version": "Latest version: {{ver}}",
"updates_checking": "'Checking",
"updates_check_button": "Check For Updates",
"updates_install_button": "Install Update",
"updates_reloading": "Reloading",
"updates_updating": "Updating"
}
@@ -0,0 +1,13 @@
{
"enabling":"Enabling",
"disabling":"Disabling",
"decky_update_available":"Update to {{tag_name}} available!",
"plugin_update_one":"Updates available for 1 plugin!",
"plugin_update_other":"Updates available for {{number}} plugins!",
"plugin_uninstall":"Uninstall {{name}}?",
"plugin_load_error":"Error loading plugin {{name}}",
"plugin_load_error_toast": "Error loading {{name}}",
"error":"Error",
"plugin_error_uninstall":"Please go to {{-icon}} in the Decky menu if you need to uninstall this plugin.",
"file_picker_cancel_text": "User canceled"
}
+12 -9
View File
@@ -27,12 +27,15 @@ import OldTabsHook from './tabs-hook.old';
import Toaster from './toaster';
import { VerInfo, callUpdaterMethod } from './updater';
import { getSetting } from './utils/settings';
import { useTranslation } from 'react-i18next';
const StorePage = lazy(() => import('./components/store/Store'));
const SettingsPage = lazy(() => import('./components/settings'));
const FilePicker = lazy(() => import('./components/modals/filepicker'));
const { t } = useTranslation('plugin-loader');
declare global {
interface Window {}
}
@@ -109,7 +112,7 @@ class PluginLoader extends Logger {
if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
this.toaster.toast({
title: 'Decky',
body: `Update to ${versionInfo?.remote?.tag_name} available!`,
body: t('decky_update_available', versionInfo?.remote?.tag_name),
onClick: () => Router.Navigate('/decky/settings'),
});
this.deckyState.setHasLoaderUpdate(true);
@@ -129,7 +132,8 @@ class PluginLoader extends Logger {
if (updates?.size > 0) {
this.toaster.toast({
title: 'Decky',
body: `Updates available for ${updates.size} plugin${updates.size > 1 ? 's' : ''}!`,
//body: `Updates available for ${updates.size} plugin${updates.size > 1 ? 's' : ''}!`,
body: t('plugin_update', updates.size.toString(10), {count: updates.size}),
onClick: () => Router.Navigate('/decky/settings/plugins'),
});
}
@@ -158,7 +162,7 @@ class PluginLoader extends Logger {
}}
>
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}>
Uninstall {name}?
{t('plugin_uninstall', name)}
</div>
</ConfirmModal>,
);
@@ -244,16 +248,15 @@ class PluginLoader extends Logger {
version: version,
});
} catch (e) {
this.error('Error loading plugin ' + name, e);
this.error(t('plugin_load_error', name), e);
const TheError: FC<{}> = () => (
<>
Error:{' '}
{t("error")}:{' '}
<pre>
<code>{e instanceof Error ? e.stack : JSON.stringify(e)}</code>
</pre>
<>
Please go to <FaCog style={{ display: 'inline' }} /> in the Decky menu if you need to uninstall this
plugin.
{t('plugin_error_uninstall', <FaCog style={{ display: 'inline' }} />)}
</>
</>
);
@@ -263,7 +266,7 @@ class PluginLoader extends Logger {
content: <TheError />,
icon: <FaExclamationCircle />,
});
this.toaster.toast({ title: 'Error loading ' + name, body: '' + e, icon: <FaExclamationCircle /> });
this.toaster.toast({ title: t('error_loading_plugin_toast', name), body: '' + e, icon: <FaExclamationCircle /> });
}
} else throw new Error(`${name} frontend_bundle not OK`);
}
@@ -301,7 +304,7 @@ class PluginLoader extends Logger {
// Purposely outside of the FilePicker component as lazy-loaded ModalRoots don't focus correctly
<ModalRoot
onCancel={() => {
reject('User canceled');
reject(t('file_picker_cancel_text'));
closeModal?.();
}}
>