🚧 wip extract to translations file

This commit is contained in:
Manuel Ruwe
2022-08-18 21:46:46 +02:00
parent 57cfb58c0b
commit ac4dc23e08
29 changed files with 1216 additions and 156 deletions

View File

@@ -22,6 +22,7 @@ import { IconApps } from '@tabler/icons';
import { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { useDebouncedValue } from '@mantine/hooks';
import { t } from 'i18next';
import { useConfig } from '../../tools/state';
import { tryMatchPort, ServiceTypeList, StatusCodes } from '../../tools/types';
import Tip from '../layout/Tip';
@@ -33,13 +34,13 @@ export function AddItemShelfButton(props: any) {
<Modal
size="xl"
radius="md"
title={<Title order={3}>Add service</Title>}
title={<Title order={3}>{t('layout.header.addService.modal.title')}</Title>}
opened={props.opened || opened}
onClose={() => setOpened(false)}
>
<AddAppShelfItemForm setOpened={setOpened} />
</Modal>
<Tooltip withinPortal label="Add a service">
<Tooltip withinPortal label={t('layout.header.addService.actionIcon.tooltip')}>
<ActionIcon
variant="default"
radius="md"
@@ -120,13 +121,13 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
try {
const _isValid = new URL(value);
} catch (e) {
return 'Please enter a valid URL';
return t('layout.header.addService.modal.form.validation.invalidUrl');
}
return null;
},
status: (value: string[]) => {
if (!value.length) {
return 'Please select a status code';
return t('layout.header.addService.modal.form.validation.noStatusCodeSelected');
}
return null;
},
@@ -203,48 +204,62 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
>
<Tabs defaultValue="Options">
<Tabs.List grow>
<Tabs.Tab value="Options">Options</Tabs.Tab>
<Tabs.Tab value="Advanced Options">Advanced options</Tabs.Tab>
<Tabs.Tab value="Options">
{t('layout.header.addService.modal.tabs.options.title')}
</Tabs.Tab>
<Tabs.Tab value="Advanced Options">
{t('layout.header.addService.modal.tabs.advancedOptions.title')}
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="Options">
<Stack>
<TextInput
required
label="Service name"
placeholder="Plex"
label={t('layout.header.addService.modal.tabs.options.form.serviceName.label')}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.serviceName.placeholder'
)}
{...form.getInputProps('name')}
/>
<TextInput
required
label="Icon URL"
label={t('layout.header.addService.modal.tabs.options.form.iconUrl.label')}
placeholder={DEFAULT_ICON}
{...form.getInputProps('icon')}
/>
<TextInput
required
label="Service URL"
label={t('layout.header.addService.modal.tabs.options.form.serviceUrl.label')}
placeholder="http://localhost:7575"
{...form.getInputProps('url')}
/>
<TextInput
label="On Click URL"
label={t('layout.header.addService.modal.tabs.options.form.onClickUrl.label')}
placeholder="http://sonarr.example.com"
{...form.getInputProps('openedUrl')}
/>
<Select
label="Service type"
defaultValue="Other"
placeholder="Pick one"
label={t('layout.header.addService.modal.tabs.options.form.serviceType.label')}
defaultValue={t(
'layout.header.addService.modal.tabs.options.form.serviceType.defaultValue'
)}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.serviceType.placeholder'
)}
required
searchable
data={ServiceTypeList}
{...form.getInputProps('type')}
/>
<Select
label="Category"
label={t('layout.header.addService.modal.tabs.options.form.category.label')}
data={categories}
placeholder="Select a category or create a new one"
nothingFound="Nothing found"
placeholder={t(
'layout.header.addService.modal.tabs.options.form.category.placeholder'
)}
nothingFound={t(
'layout.header.addService.modal.tabs.options.form.category.nothingFound'
)}
searchable
clearable
creatable
@@ -253,7 +268,11 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
setCategories([...InitialCategories, query]);
return item;
}}
getCreateLabel={(query) => `+ Create "${query}"`}
getCreateLabel={(query) =>
t('layout.header.addService.modal.tabs.options.form.category.createLabel', {
query,
})
}
{...form.getInputProps('category')}
/>
<LoadingOverlay visible={isLoading} />
@@ -266,23 +285,36 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
<>
<TextInput
required
label="API key"
placeholder="Your API key"
label={t(
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.label'
)}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.placeholder'
)}
value={form.values.apiKey}
onChange={(event) => {
form.setFieldValue('apiKey', event.currentTarget.value);
}}
error={form.errors.apiKey && 'Invalid API key'}
error={
form.errors.apiKey &&
t(
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.validation.noKey'
)
}
/>
<Tip>
Get your API key{' '}
{t(
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.tip.text'
)}{' '}
<Anchor
target="_blank"
weight="bold"
style={{ fontStyle: 'inherit', fontSize: 'inherit' }}
href={`${hostname}/settings/general`}
>
here.
{t(
'layout.header.addService.modal.tabs.options.form.integrations.apiKey.tip.link'
)}
</Anchor>
</Tip>
</>
@@ -291,79 +323,134 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
<>
<TextInput
required
label="Username"
placeholder="admin"
label={t(
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.username.label'
)}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.username.placeholder'
)}
value={form.values.username}
onChange={(event) => {
form.setFieldValue('username', event.currentTarget.value);
}}
error={form.errors.username && 'Invalid username'}
error={
form.errors.username &&
t(
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.username.validation.invalidUsername'
)
}
/>
<PasswordInput
required
label="Password"
placeholder="adminadmin"
label={t(
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.password.label'
)}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.password.placeholder'
)}
value={form.values.password}
onChange={(event) => {
form.setFieldValue('password', event.currentTarget.value);
}}
error={form.errors.password && 'Invalid password'}
error={
form.errors.password &&
t(
'layout.header.addService.modal.tabs.options.form.integrations.qBittorrent.password.validation.invalidPassword'
)
}
/>
</>
)}
{form.values.type === 'Deluge' && (
<>
<PasswordInput
label="Password"
placeholder="password"
label={t(
'layout.header.addService.modal.tabs.options.form.integrations.deluge.password.label'
)}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.integrations.deluge.password.placeholder'
)}
value={form.values.password}
onChange={(event) => {
form.setFieldValue('password', event.currentTarget.value);
}}
error={form.errors.password && 'Invalid password'}
error={
form.errors.password &&
t(
'layout.header.addService.modal.tabs.options.form.integrations.deluge.password.validation.invalidPassword'
)
}
/>
</>
)}
{form.values.type === 'Transmission' && (
<>
<TextInput
label="Username"
placeholder="admin"
label={t(
'layout.header.addService.modal.tabs.options.form.integrations.transmission.username.label'
)}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.integrations.transmission.username.placeholder'
)}
value={form.values.username}
onChange={(event) => {
form.setFieldValue('username', event.currentTarget.value);
}}
error={form.errors.username && 'Invalid username'}
error={
form.errors.username &&
t(
'layout.header.addService.modal.tabs.options.form.integrations.transmission.username.validation.invalidUsername'
)
}
/>
<PasswordInput
label="Password"
placeholder="adminadmin"
label={t(
'layout.header.addService.modal.tabs.options.form.integrations.transmission.password.label'
)}
placeholder={t(
'layout.header.addService.modal.tabs.options.form.integrations.transmission.password.placeholder'
)}
value={form.values.password}
onChange={(event) => {
form.setFieldValue('password', event.currentTarget.value);
}}
error={form.errors.password && 'Invalid password'}
error={
form.errors.password &&
t(
'layout.header.addService.modal.tabs.options.form.integrations.transmission.password.validation.invalidPassword'
)
}
/>
</>
)}
</Stack>
</Tabs.Panel>
<Tabs.Panel value="Advanced Options">
<Tabs.Panel value={t('layout.header.addService.modal.tabs.advancedOptions.title')}>
<Stack>
<MultiSelect
required
label="HTTP Status Codes"
label={t(
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.label'
)}
data={StatusCodes}
placeholder="Select valid status codes"
clearButtonLabel="Clear selection"
nothingFound="Nothing found"
placeholder={t(
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.placeholder'
)}
clearButtonLabel={t(
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.clearButtonLabel'
)}
nothingFound={t(
'layout.header.addService.modal.tabs.advancedOptions.form.httpStatusCodes.nothingFound'
)}
defaultValue={['200']}
clearable
searchable
{...form.getInputProps('status')}
/>
<Switch
label="Open service in new tab"
label={t(
'layout.header.addService.modal.tabs.advancedOptions.form.openServiceInNewTab.label'
)}
defaultChecked={form.values.newTab}
{...form.getInputProps('newTab')}
/>
@@ -371,7 +458,10 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
</Tabs.Panel>
</Tabs>
<Group grow position="center" mt="xl">
<Button type="submit">{props.message ?? 'Add service'}</Button>
<Button type="submit">
{props.message ??
t('layout.header.addService.modal.tabs.advancedOptions.form.buttons.submit.content')}
</Button>
</Group>
</form>
</>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { createStyles, Switch, Group, useMantineColorScheme, Kbd } from '@mantine/core';
import { IconSun as Sun, IconMoonStars as MoonStars } from '@tabler/icons';
import { useConfig } from '../../tools/state';
import { t } from 'i18next';
const useStyles = createStyles((theme) => ({
root: {
@@ -41,7 +42,9 @@ export function ColorSchemeSwitch() {
<MoonStars className={cx(classes.icon, classes.iconDark)} size={18} />
<Switch checked={colorScheme === 'dark'} onChange={() => toggleColorScheme()} size="md" />
</div>
Switch to {colorScheme === 'dark' ? 'light' : 'dark'} mode
{t('settings.tabs.common.settings.colorScheme.label', {
scheme: colorScheme === 'dark' ? 'light' : 'dark',
})}
<Group spacing={2}>
<Kbd>Ctrl</Kbd>+<Kbd>J</Kbd>
</Group>

View File

@@ -1,5 +1,6 @@
import { Center, Loader, Select, Tooltip } from '@mantine/core';
import { setCookie } from 'cookies-next';
import { t } from 'i18next';
import { useEffect, useState } from 'react';
import { useConfig } from '../../tools/state';
@@ -23,7 +24,7 @@ export default function ConfigChanger() {
// return <Select data={[{ value: '1', label: '1' },]} onChange={(e) => console.log(e)} value="1" />;
return (
<Select
label="Config loader"
label={t('settings.tabs.common.settings.configChanger.configSelect.label')}
value={value}
defaultValue={config.name}
onChange={(e) => {

View File

@@ -12,6 +12,7 @@ import {
IconX as X,
} from '@tabler/icons';
import { useConfig } from '../../tools/state';
import { t } from 'i18next';
export default function SaveConfigComponent(props: any) {
const [opened, setOpened] = useState(false);
@@ -32,7 +33,7 @@ export default function SaveConfigComponent(props: any) {
radius="md"
opened={opened}
onClose={() => setOpened(false)}
title="Choose the name of your new config"
title={t('settings.tabs.common.settings.configChanger.modal.title')}
>
<form
onSubmit={form.onSubmit((values) => {
@@ -50,17 +51,21 @@ export default function SaveConfigComponent(props: any) {
>
<TextInput
required
label="Config name"
placeholder="Your new config name"
label={t('settings.tabs.common.settings.configChanger.modal.form.configName.label')}
placeholder={t(
'settings.tabs.common.settings.configChanger.modal.form.configName.placeholder'
)}
{...form.getInputProps('configName')}
/>
<Group position="right" mt="md">
<Button type="submit">Confirm</Button>
<Button type="submit">
{t('settings.tabs.common.settings.configChanger.modal.form.buttons.submit')}
</Button>
</Group>
</form>
</Modal>
<Button size="xs" leftIcon={<Download />} variant="outline" onClick={onClick}>
Download config
{t('settings.tabs.common.settings.configChanger.buttons.download')}
</Button>
<Button
size="xs"
@@ -71,31 +76,39 @@ export default function SaveConfigComponent(props: any) {
.delete(`/api/configs/${config.name}`)
.then(() => {
showNotification({
title: 'Config deleted',
title: t(
'settings.tabs.common.settings.configChanger.buttons.delete.deleted.title'
),
icon: <Check />,
color: 'green',
autoClose: 1500,
radius: 'md',
message: 'Config deleted',
message: t(
'settings.tabs.common.settings.configChanger.buttons.delete.deleted.message'
),
});
})
.catch(() => {
showNotification({
title: 'Config delete failed',
title: t(
'settings.tabs.common.settings.configChanger.buttons.delete.deleteFailed.title'
),
icon: <X />,
color: 'red',
autoClose: 1500,
radius: 'md',
message: 'Config delete failed',
message: t(
'settings.tabs.common.settings.configChanger.buttons.delete.deleteFailed.message'
),
});
});
setConfig({ ...config, name: 'default' });
}}
>
Delete config
{t('settings.tabs.common.settings.configChanger.buttons.delete.text')}
</Button>
<Button size="xs" leftIcon={<Plus />} variant="outline" onClick={() => setOpened(true)}>
Save a copy
{t('settings.tabs.common.settings.configChanger.buttons.saveCopy')}
</Button>
</Group>
);

View File

@@ -5,6 +5,7 @@ import { ColorSelector } from './ColorSelector';
import { OpacitySelector } from './OpacitySelector';
import { AppCardWidthSelector } from './AppCardWidthSelector';
import { ShadeSelector } from './ShadeSelector';
import { t } from 'i18next';
export default function TitleChanger() {
const { config, setConfig } = useConfig();
@@ -40,19 +41,27 @@ export default function TitleChanger() {
<Stack mb="md" mr="sm" mt="xs">
<form onSubmit={form.onSubmit((values) => saveChanges(values))}>
<Stack>
<TextInput label="Page title" placeholder="Homarr 🦞" {...form.getInputProps('title')} />
<TextInput label="Logo" placeholder="/img/logo.png" {...form.getInputProps('logo')} />
<TextInput
label="Favicon"
placeholder="/favicon.png"
label={t('settings.tabs.customizations.settings.pageTitle.label')}
placeholder={t('settings.tabs.customizations.settings.pageTitle.placeholder')}
{...form.getInputProps('title')}
/>
<TextInput
label={t('settings.tabs.customizations.settings.logo.label')}
placeholder={t('settings.tabs.customizations.settings.logo.placeholder')}
{...form.getInputProps('logo')}
/>
<TextInput
label={t('settings.tabs.customizations.settings.favicon.label')}
placeholder={t('settings.tabs.customizations.settings.favicon.placeholder')}
{...form.getInputProps('favicon')}
/>
<TextInput
label="Background"
placeholder="/img/background.png"
label={t('settings.tabs.customizations.settings.background.label')}
placeholder={t('settings.tabs.customizations.settings.background.placeholder')}
{...form.getInputProps('background')}
/>
<Button type="submit">Save</Button>
<Button type="submit">{t('settings.tabs.customizations.settings.buttons.submit')}</Button>
</Stack>
</form>
<ColorSelector type="primary" />

View File

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { ColorSwatch, Grid, Group, Popover, Text, useMantineTheme } from '@mantine/core';
import { useConfig } from '../../tools/state';
import { useColorTheme } from '../../tools/color';
import { t } from 'i18next';
interface ColorControlProps {
type: string;
@@ -82,7 +83,11 @@ export function ColorSelector({ type }: ColorControlProps) {
</Grid>
</Popover.Dropdown>
</Popover>
<Text>{type[0].toUpperCase() + type.slice(1)} color</Text>
<Text>
{t('settings.tabs.customizations.settings.colorSelector.suffix', {
color: type[0].toUpperCase() + type.slice(1),
})}
</Text>
</Group>
);
}

View File

@@ -7,6 +7,8 @@ import ConfigChanger from '../Config/ConfigChanger';
import SaveConfigComponent from '../Config/SaveConfig';
import ModuleEnabler from './ModuleEnabler';
import Tip from '../layout/Tip';
import { t } from 'i18next';
import LanguageSwitch from './LanguageSwitch';
export default function CommonSettings(args: any) {
const { config, setConfig } = useConfig();
@@ -26,15 +28,12 @@ export default function CommonSettings(args: any) {
return (
<Stack mb="md" mr="sm">
<Stack spacing={0} mt="xs">
<Text>Search engine</Text>
<Tip>
Use the prefixes <b>!yt</b> and <b>!t</b> in front of your query to search on YouTube or
for a Torrent respectively.
</Tip>
<Text>{t('settings.tabs.common.settings.searchEngine.title')}</Text>
<Tip>{t('settings.tabs.common.settings.searchEngine.tips.generalTip')}</Tip>
<SegmentedControl
fullWidth
mb="sm"
title="Search engine"
title={t('settings.tabs.common.settings.searchEngine.title')}
value={
// Match config.settings.searchUrl with a key in the matches array
searchUrl
@@ -56,10 +55,10 @@ export default function CommonSettings(args: any) {
/>
{searchUrl === 'Custom' && (
<>
<Tip>%s can be used as a placeholder for the query.</Tip>
<Tip>{t('settings.tabs.common.settings.searchEngine.tips.placeholderTip')}</Tip>
<TextInput
label="Query URL"
placeholder="Custom query URL"
label={t('settings.tabs.common.settings.searchEngine.customEngine.label')}
placeholder={t('settings.tabs.common.settings.searchEngine.customEngine.placeholder')}
value={customSearchUrl}
onChange={(event) => {
setCustomSearchUrl(event.currentTarget.value);
@@ -78,9 +77,10 @@ export default function CommonSettings(args: any) {
<ColorSchemeSwitch />
<WidgetsPositionSwitch />
<ModuleEnabler />
<LanguageSwitch />
<ConfigChanger />
<SaveConfigComponent />
<Tip>Upload your config file by dragging and dropping it onto the page!</Tip>
<Tip>{t('settings.tabs.common.settings.configTip')}</Tip>
</Stack>
);
}

View File

@@ -1,5 +1,7 @@
import { Group, ActionIcon, Anchor, Text } from '@mantine/core';
import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons';
import { t } from 'i18next';
import { CURRENT_VERSION } from '../../../data/constants';
export default function Credits(props: any) {
@@ -27,7 +29,7 @@ export default function Credits(props: any) {
color: 'gray',
}}
>
Made with by @
{t('settings.credits.madeWithLove')}
<Anchor
href="https://github.com/ajnart"
style={{ color: 'inherit', fontStyle: 'inherit', fontSize: 'inherit' }}

View File

@@ -0,0 +1,78 @@
import { Group, Image, Select, Stack, Text } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import { IconLanguage } from '@tabler/icons';
import { forwardRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { convertCodeToName } from '../../translations/i18n';
export default function LanguageSwitch() {
const { t, i18n } = useTranslation();
const { language, languages, changeLanguage } = i18n;
const [selectedLanguage, setSelectedLanguage] = useState<string | null>(language);
const data = languages.map((language) => ({
image: `https://countryflagsapi.com/png/${language.split('-').pop()}`,
label: convertCodeToName(language),
value: language,
}));
const onChangeSelect = (value: string) => {
setSelectedLanguage(value);
const languageName = convertCodeToName(value);
changeLanguage(value)
.then(() => {
showNotification({
title: 'Language changed',
message: `You changed the language to '${languageName}'`,
color: 'green',
autoClose: 5000,
});
})
.catch((err) => {
showNotification({
title: 'Failed to change language',
message: `Failed to change to '${languageName}', Error:'${err}`,
color: 'red',
autoClose: 5000,
});
});
};
return (
<Stack>
<Select
icon={<IconLanguage size={18} />}
label={t('settings.tabs.common.settings.language.title')}
data={data}
itemComponent={SelectItem}
nothingFound="Nothing found"
onChange={onChangeSelect}
value={selectedLanguage}
defaultValue={language}
/>
</Stack>
);
}
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
image: string;
label: string;
}
const SelectItem = forwardRef<HTMLDivElement, ItemProps>(
({ image, label, ...others }: ItemProps, ref) => (
<div ref={ref} {...others}>
<Group noWrap>
<Image src={image} width={30} height={20} radius="xs" />
<div>
<Text size="sm">{label}</Text>
</div>
</Group>
</div>
)
);

View File

@@ -1,4 +1,5 @@
import { Checkbox, SimpleGrid, Stack, Title } from '@mantine/core';
import { t } from 'i18next';
import * as Modules from '../../modules';
import { useConfig } from '../../tools/state';
@@ -7,7 +8,7 @@ export default function ModuleEnabler(props: any) {
const modules = Object.values(Modules).map((module) => module);
return (
<Stack>
<Title order={4}>Module enabler</Title>
<Title order={4}>{t('settings.tabs.common.settings.moduleEnabler.title')}</Title>
<SimpleGrid cols={3} spacing="xs">
{modules.map((module) => (
<Checkbox

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { Text, Slider, Stack } from '@mantine/core';
import { useConfig } from '../../tools/state';
import { t } from 'i18next';
export function OpacitySelector() {
const { config, setConfig } = useConfig();
@@ -30,7 +31,7 @@ export function OpacitySelector() {
return (
<Stack spacing="xs">
<Text>App Opacity</Text>
<Text>{t('settings.tabs.customizations.settings.opacitySelector.label')}</Text>
<Slider
defaultValue={config.settings.appOpacity || 100}
step={10}

View File

@@ -2,6 +2,8 @@ import { ActionIcon, Title, Tooltip, Drawer, Tabs, ScrollArea } from '@mantine/c
import { useHotkeys } from '@mantine/hooks';
import { useState } from 'react';
import { IconSettings } from '@tabler/icons';
import { t } from 'i18next';
import AdvancedSettings from './AdvancedSettings';
import CommonSettings from './CommonSettings';
import Credits from './Credits';
@@ -10,8 +12,8 @@ function SettingsMenu(props: any) {
return (
<Tabs defaultValue="Common">
<Tabs.List grow>
<Tabs.Tab value="Common">Common</Tabs.Tab>
<Tabs.Tab value="Customizations">Customizations</Tabs.Tab>
<Tabs.Tab value="Common">{t('settings.tabs.common.title')}</Tabs.Tab>
<Tabs.Tab value="Customizations">{t('settings.tabs.customizations.title')}</Tabs.Tab>
</Tabs.List>
<Tabs.Panel data-autofocus value="Common">
<ScrollArea style={{ height: '78vh' }} offsetScrollbars>
@@ -37,14 +39,14 @@ export function SettingsMenuButton(props: any) {
size="xl"
padding="lg"
position="right"
title={<Title order={5}>Settings</Title>}
title={<Title order={5}>{t('settings.title')}</Title>}
opened={props.opened || opened}
onClose={() => setOpened(false)}
>
<SettingsMenu />
<Credits />
</Drawer>
<Tooltip label="Settings">
<Tooltip label={t('settings.tooltip')}>
<ActionIcon
variant="default"
radius="md"

View File

@@ -11,6 +11,7 @@ import {
} from '@mantine/core';
import { useConfig } from '../../tools/state';
import { useColorTheme } from '../../tools/color';
import { t } from 'i18next';
export function ShadeSelector() {
const { config, setConfig } = useConfig();
@@ -94,7 +95,7 @@ export function ShadeSelector() {
</Stack>
</Popover.Dropdown>
</Popover>
<Text>Shade</Text>
<Text>{t('settings.tabs.customizations.settings.shadeSelector.label')}</Text>
</Group>
);
}

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { createStyles, Switch, Group } from '@mantine/core';
import { useConfig } from '../../tools/state';
import { t } from 'i18next';
const useStyles = createStyles((theme) => ({
root: {
@@ -54,7 +55,7 @@ export function WidgetsPositionSwitch() {
size="md"
/>
</div>
Position widgets on left
{t('settings.tabs.common.settings.widgetsPositionSwitch.label')}
</Group>
);
}