Add localization support (#333)

* Add updated i18n config and en locale
This commit is contained in:
Jeff 2023-10-30 19:22:45 -07:00 committed by GitHub
parent 11863fd4c1
commit 8430b1ec95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 2679 additions and 908 deletions

View file

@ -1,7 +1,7 @@
import { useMemo, useState } from 'react';
import { Box, Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form';
import { closeModal, ContextModalProps } from '@mantine/modals';
import { useMemo, useState } from 'react';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { PlaylistListSort, SongListQuery, SongListSort, SortOrder } from '/@/renderer/api/types';
@ -11,6 +11,7 @@ import { useAddToPlaylist } from '/@/renderer/features/playlists/mutations/add-t
import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query';
import { queryClient } from '/@/renderer/lib/react-query';
import { useCurrentServer } from '/@/renderer/store';
import { useTranslation } from 'react-i18next';
export const AddToPlaylistContextModal = ({
id,
@ -21,6 +22,7 @@ export const AddToPlaylistContextModal = ({
genreId?: string[];
songId?: string[];
}>) => {
const { t } = useTranslation();
const { albumId, artistId, genreId, songId } = innerProps;
const server = useCurrentServer();
const [isLoading, setIsLoading] = useState(false);
@ -140,7 +142,10 @@ export const AddToPlaylistContextModal = ({
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId, query);
const playlistSongsRes = await queryClient.fetchQuery(queryKey, ({ signal }) => {
if (!server) throw new Error('No server');
if (!server)
throw new Error(
t('error.serverNotSelectedError', { postProcess: 'sentenceCase' }),
);
return api.controller.getPlaylistSongList({
apiClientProps: {
server,
@ -175,7 +180,7 @@ export const AddToPlaylistContextModal = ({
playlistSelect.find((playlist) => playlist.value === playlistId)
?.label
}] ${err.message}`,
title: 'Failed to add songs to playlist',
title: t('error.genericError', { postProcess: 'sentenceCase' }),
});
},
},
@ -186,12 +191,16 @@ export const AddToPlaylistContextModal = ({
const addMessage =
values.skipDuplicates &&
allSongIds.length * values.playlistId.length !== totalUniquesAdded
? `around ${Math.floor(totalUniquesAdded / values.playlistId.length)}`
? `${Math.floor(totalUniquesAdded / values.playlistId.length)}`
: allSongIds.length;
setIsLoading(false);
toast.success({
message: `Added ${addMessage} songs to ${values.playlistId.length} playlist(s)`,
message: t('form.addToPlaylist', {
message: addMessage,
numOfPlaylists: values.playlistId.length,
postProcess: 'sentenceCase',
}),
});
closeModal(id);
return null;
@ -206,12 +215,18 @@ export const AddToPlaylistContextModal = ({
searchable
data={playlistSelect}
disabled={playlistList.isLoading}
label="Playlists"
label={t('form.addToPlaylist.input', {
context: 'playlists',
postProcess: 'titleCase',
})}
size="md"
{...form.getInputProps('playlistId')}
/>
<Switch
label="Skip duplicates"
label={t('form.addToPlaylist.input', {
context: 'skipDuplicates',
postProcess: 'titleCase',
})}
{...form.getInputProps('skipDuplicates', { type: 'checkbox' })}
/>
<Group position="right">
@ -222,7 +237,7 @@ export const AddToPlaylistContextModal = ({
variant="subtle"
onClick={() => closeModal(id)}
>
Cancel
{t('common.cancel', { postProcess: 'titleCase' })}
</Button>
<Button
disabled={isSubmitDisabled}
@ -231,7 +246,7 @@ export const AddToPlaylistContextModal = ({
type="submit"
variant="filled"
>
Add
{t('common.add', { postProcess: 'titleCase' })}
</Button>
</Group>
</Group>

View file

@ -1,6 +1,6 @@
import { useRef, useState } from 'react';
import { Group, Stack } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useRef, useState } from 'react';
import { CreatePlaylistBody, ServerType, SongListSort } from '/@/renderer/api/types';
import { Button, Switch, Text, TextInput, toast } from '/@/renderer/components';
import {
@ -10,12 +10,14 @@ import {
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
import { convertQueryGroupToNDQuery } from '/@/renderer/features/playlists/utils';
import { useCurrentServer } from '/@/renderer/store';
import { useTranslation } from 'react-i18next';
interface CreatePlaylistFormProps {
onCancel: () => void;
}
export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
const { t } = useTranslation();
const mutation = useCreatePlaylist({});
const server = useCurrentServer();
const queryBuilderRef = useRef<PlaylistQueryBuilderRef>(null);
@ -69,10 +71,15 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
},
{
onError: (err) => {
toast.error({ message: err.message, title: 'Error creating playlist' });
toast.error({
message: err.message,
title: t('error.genericError', { postProcess: 'sentenceCase' }),
});
},
onSuccess: () => {
toast.success({ message: `Playlist has been created` });
toast.success({
message: t('form.createPlaylist.success', { postProcess: 'sentenceCase' }),
});
onCancel();
},
},
@ -88,17 +95,26 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
<TextInput
data-autofocus
required
label="Name"
label={t('form.createPlaylist.input', {
context: 'name',
postProcess: 'titleCase',
})}
{...form.getInputProps('name')}
/>
<TextInput
label="Description"
label={t('form.createPlaylist.input', {
context: 'description',
postProcess: 'titleCase',
})}
{...form.getInputProps('comment')}
/>
<Group>
{isPublicDisplayed && (
<Switch
label="Is public?"
label={t('form.createPlaylist.input', {
context: 'public',
postProcess: 'titleCase',
})}
{...form.getInputProps('_custom.navidrome.public', {
type: 'checkbox',
})}
@ -130,7 +146,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
variant="subtle"
onClick={onCancel}
>
Cancel
{t('common.cancel', { postProcess: 'titleCase' })}
</Button>
<Button
disabled={isSubmitDisabled}
@ -138,7 +154,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
type="submit"
variant="filled"
>
Save
{t('common.create', { postProcess: 'titleCase' })}
</Button>
</Group>
</Stack>

View file

@ -1,8 +1,9 @@
import { MutableRefObject, useMemo, useRef } from 'react';
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Box, Group } from '@mantine/core';
import { closeAllModals, openModal } from '@mantine/modals';
import { MutableRefObject, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { RiMoreFill } from 'react-icons/ri';
import { generatePath, useNavigate, useParams } from 'react-router';
import { Link } from 'react-router-dom';
@ -45,6 +46,7 @@ interface PlaylistDetailContentProps {
}
export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { playlistId } = useParams() as { playlistId: string };
const { table } = useListStoreByKey({ key: LibraryItem.SONG });
@ -102,13 +104,10 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
onError: (err) => {
toast.error({
message: err.message,
title: 'Error deleting playlist',
title: t('error.genericError', { postProcess: 'sentenceCase' }),
});
},
onSuccess: () => {
toast.success({
message: `Playlist has been deleted`,
});
closeAllModals();
navigate(AppRoute.PLAYLISTS);
},
@ -126,7 +125,7 @@ export const PlaylistDetailContent = ({ tableRef }: PlaylistDetailContentProps)
Are you sure you want to delete this playlist?
</ConfirmModal>
),
title: 'Delete playlist',
title: t('form.deletePlaylist.title', { postProcess: 'sentenceCase' }),
});
};

View file

@ -4,6 +4,7 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
import { Divider, Flex, Group, Stack } from '@mantine/core';
import { closeAllModals, openModal } from '@mantine/modals';
import { useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import {
RiMoreFill,
RiSettings3Fill,
@ -101,6 +102,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
tableRef,
handleToggleShowQueryBuilder,
}: PlaylistDetailSongListHeaderFiltersProps) => {
const { t } = useTranslation();
const { playlistId } = useParams() as { playlistId: string };
const navigate = useNavigate();
const queryClient = useQueryClient();
@ -267,19 +269,16 @@ export const PlaylistDetailSongListHeaderFilters = ({
onError: (err) => {
toast.error({
message: err.message,
title: 'Error deleting playlist',
title: t('error.genericError', { postProcess: 'sentenceCase' }),
});
},
onSuccess: () => {
toast.success({
message: `Playlist has been deleted`,
});
navigate(AppRoute.PLAYLISTS, { replace: true });
},
},
);
closeAllModals();
}, [deletePlaylistMutation, detailQuery.data, navigate]);
}, [deletePlaylistMutation, detailQuery.data, navigate, t]);
const openDeletePlaylistModal = () => {
openModal({
@ -288,7 +287,7 @@ export const PlaylistDetailSongListHeaderFilters = ({
<Text>Are you sure you want to delete this playlist?</Text>
</ConfirmModal>
),
title: 'Delete playlist(s)',
title: t('form.deletePlaylist.title', { postProcess: 'sentenceCase' }),
});
};
@ -345,19 +344,19 @@ export const PlaylistDetailSongListHeaderFilters = ({
icon={<RiPlayFill />}
onClick={() => handlePlay(Play.NOW)}
>
Play
{t('player.play', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
<DropdownMenu.Item
icon={<RiAddBoxFill />}
onClick={() => handlePlay(Play.LAST)}
>
Add to queue
{t('player.addLast', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
<DropdownMenu.Item
icon={<RiAddCircleFill />}
onClick={() => handlePlay(Play.NEXT)}
>
Add to queue next
{t('player.addNext', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
<DropdownMenu.Divider />
<DropdownMenu.Item
@ -369,20 +368,20 @@ export const PlaylistDetailSongListHeaderFilters = ({
})
}
>
Edit playlist
{t('action.editPlaylist', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
<DropdownMenu.Item
icon={<RiDeleteBinFill />}
onClick={openDeletePlaylistModal}
>
Delete playlist
{t('action.deletePlaylist', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
<DropdownMenu.Divider />
<DropdownMenu.Item
icon={<RiRefreshLine />}
onClick={handleRefresh}
>
Refresh
{t('action.refresh', { postProcess: 'sentenceCase' })}
</DropdownMenu.Item>
{server?.type === ServerType.NAVIDROME && !isSmartPlaylist && (
<>
@ -391,7 +390,9 @@ export const PlaylistDetailSongListHeaderFilters = ({
$danger
onClick={handleToggleShowQueryBuilder}
>
Toggle smart playlist editor
{t('action.toggleSmartPlaylistEditor', {
postProcess: 'sentenceCase',
})}
</DropdownMenu.Item>
</>
)}

View file

@ -1,6 +1,7 @@
import { MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Stack } from '@mantine/core';
import { MutableRefObject } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
import { LibraryItem } from '/@/renderer/api/types';
import { Badge, PageHeader, Paper, SpinnerIcon } from '/@/renderer/components';
@ -23,6 +24,7 @@ export const PlaylistDetailSongListHeader = ({
itemCount,
handleToggleShowQueryBuilder,
}: PlaylistDetailHeaderProps) => {
const { t } = useTranslation();
const { playlistId } = useParams() as { playlistId: string };
const server = useCurrentServer();
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
@ -58,7 +60,7 @@ export const PlaylistDetailSongListHeader = ({
itemCount
)}
</Paper>
{isSmartPlaylist && <Badge size="lg">Smart playlist</Badge>}
{isSmartPlaylist && <Badge size="lg">{t('entity.smartPlaylist')}</Badge>}
</LibraryHeaderBar>
</PageHeader>
<Paper p="1rem">

View file

@ -1,8 +1,9 @@
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Divider, Flex, Group, Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query';
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { RiMoreFill, RiRefreshLine, RiSettings3Fill } from 'react-icons/ri';
import { useListContext } from '../../../context/list-context';
import { useListStoreByKey } from '../../../store/list.store';
@ -42,6 +43,7 @@ export const PlaylistListHeaderFilters = ({
gridRef,
tableRef,
}: PlaylistListHeaderFiltersProps) => {
const { t } = useTranslation();
const { pageKey } = useListContext();
const queryClient = useQueryClient();
const server = useCurrentServer();
@ -285,7 +287,7 @@ export const PlaylistListHeaderFilters = ({
<Button
compact
size="md"
tooltip={{ label: 'Refresh' }}
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
variant="subtle"
onClick={handleRefresh}
>
@ -308,7 +310,7 @@ export const PlaylistListHeaderFilters = ({
icon={<RiRefreshLine />}
onClick={handleRefresh}
>
Refresh
{t('common.refresh', { postProcess: 'titleCase' })}
</DropdownMenu.Item>
</DropdownMenu.Dropdown>
</DropdownMenu>
@ -328,27 +330,29 @@ export const PlaylistListHeaderFilters = ({
</Button>
</DropdownMenu.Target>
<DropdownMenu.Dropdown>
<DropdownMenu.Label>Display type</DropdownMenu.Label>
<DropdownMenu.Label>
{t('table.config.general.displayType', { postProcess: 'titleCase' })}
</DropdownMenu.Label>
<DropdownMenu.Item
$isActive={display === ListDisplayType.CARD}
value={ListDisplayType.CARD}
onClick={handleSetViewType}
>
Card
{t('table.config.view.card', { postProcess: 'titleCase' })}
</DropdownMenu.Item>
<DropdownMenu.Item
$isActive={display === ListDisplayType.POSTER}
value={ListDisplayType.POSTER}
onClick={handleSetViewType}
>
Poster
{t('table.config.view.poster', { postProcess: 'titleCase' })}
</DropdownMenu.Item>
<DropdownMenu.Item
$isActive={display === ListDisplayType.TABLE}
value={ListDisplayType.TABLE}
onClick={handleSetViewType}
>
Table
{t('table.config.view.table', { postProcess: 'titleCase' })}
</DropdownMenu.Item>
{/* <DropdownMenu.Item
$isActive={display === ListDisplayType.TABLE_PAGINATED}
@ -382,7 +386,11 @@ export const PlaylistListHeaderFilters = ({
</DropdownMenu.Item>
{!isGrid && (
<>
<DropdownMenu.Label>Table Columns</DropdownMenu.Label>
<DropdownMenu.Label>
{t('table.config.generaltableColumns', {
postProcess: 'titleCase',
})}
</DropdownMenu.Label>
<DropdownMenu.Item
closeMenuOnClick={false}
component="div"
@ -399,7 +407,11 @@ export const PlaylistListHeaderFilters = ({
onChange={handleTableColumns}
/>
<Group position="apart">
<Text>Auto Fit Columns</Text>
<Text>
{t('table.config.general.autoFitColumns', {
postProcess: 'titleCase',
})}
</Text>
<Switch
defaultChecked={table.autoFit}
onChange={handleAutoFitColumns}

View file

@ -11,6 +11,7 @@ import { useContainerQuery } from '/@/renderer/hooks';
import { PlaylistListFilter, useCurrentServer, useListStoreActions } from '/@/renderer/store';
import { ListDisplayType, ServerType } from '/@/renderer/types';
import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next';
import { RiFileAddFill } from 'react-icons/ri';
import { LibraryItem } from '/@/renderer/api/types';
import { useListFilterRefresh } from '../../../hooks/use-list-filter-refresh';
@ -24,6 +25,7 @@ interface PlaylistListHeaderProps {
}
export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistListHeaderProps) => {
const { t } = useTranslation();
const { pageKey } = useListContext();
const cq = useContainerQuery();
const server = useCurrentServer();
@ -37,7 +39,7 @@ export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistLis
tableRef?.current?.api?.purgeInfiniteCache();
},
size: server?.type === ServerType?.NAVIDROME ? 'xl' : 'sm',
title: 'Create Playlist',
title: t('form.createPlaylist.title', { postProcess: 'sentenceCase' }),
});
};
@ -74,7 +76,9 @@ export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistLis
w="100%"
>
<LibraryHeaderBar>
<LibraryHeaderBar.Title>Playlists</LibraryHeaderBar.Title>
<LibraryHeaderBar.Title>
{t('page.playlistList.title', { postProcess: 'titleCase' })}
</LibraryHeaderBar.Title>
<Paper
fw="600"
px="1rem"
@ -88,7 +92,10 @@ export const PlaylistListHeader = ({ itemCount, tableRef, gridRef }: PlaylistLis
)}
</Paper>
<Button
tooltip={{ label: 'Create playlist', openDelay: 500 }}
tooltip={{
label: t('action.createPlaylist', { postProcess: 'sentenceCase' }),
openDelay: 500,
}}
variant="filled"
onClick={handleCreatePlaylistModal}
>

View file

@ -19,6 +19,7 @@ import {
convertQueryGroupToNDQuery,
} from '/@/renderer/features/playlists/utils';
import { QueryBuilderGroup, QueryBuilderRule } from '/@/renderer/types';
import { useTranslation } from 'react-i18next';
import { RiMore2Fill, RiSaveLine } from 'react-icons/ri';
import { SongListSort } from '/@/renderer/api/types';
import {
@ -86,6 +87,7 @@ export const PlaylistQueryBuilder = forwardRef(
{ sortOrder, sortBy, limit, isSaving, query, onSave, onSaveAs }: PlaylistQueryBuilderProps,
ref: Ref<PlaylistQueryBuilderRef>,
) => {
const { t } = useTranslation();
const [filters, setFilters] = useState<QueryBuilderGroup>(
query ? convertNDQueryToQueryGroup(query) : DEFAULT_QUERY,
);
@ -354,7 +356,11 @@ export const PlaylistQueryBuilder = forwardRef(
};
const sortOptions = [
{ label: 'Random', type: 'string', value: 'random' },
{
label: t('filter.random', { postProcess: 'titleCase' }),
type: 'string',
value: 'random',
},
...NDSongQueryFields,
];
@ -414,21 +420,21 @@ export const PlaylistQueryBuilder = forwardRef(
<Select
data={[
{
label: 'Ascending',
label: t('common.ascending', { postProcess: 'titleCase' }),
value: 'asc',
},
{
label: 'Descending',
label: t('common.descending', { postProcess: 'titleCase' }),
value: 'desc',
},
]}
label="Order"
label={t('common.order', { postProcess: 'titleCase' })}
maxWidth="20%"
width={125}
{...extraFiltersForm.getInputProps('sortOrder')}
/>
<NumberInput
label="Limit"
label={t('common.limit', { postProcess: 'titleCase' })}
maxWidth="20%"
width={75}
{...extraFiltersForm.getInputProps('limit')}
@ -444,7 +450,7 @@ export const PlaylistQueryBuilder = forwardRef(
variant="filled"
onClick={handleSaveAs}
>
Save as
{t('common.saveAs', { postProcess: 'titleCase' })}
</Button>
<DropdownMenu position="bottom-end">
<DropdownMenu.Target>
@ -462,7 +468,7 @@ export const PlaylistQueryBuilder = forwardRef(
icon={<RiSaveLine color="var(--danger-color)" />}
onClick={handleSave}
>
Save and replace
{t('common.saveAndReplace', { postProcess: 'titleCase' })}
</DropdownMenu.Item>
</DropdownMenu.Dropdown>
</DropdownMenu>

View file

@ -4,6 +4,7 @@ import { CreatePlaylistBody, CreatePlaylistResponse, ServerType } from '/@/rende
import { Button, Switch, TextInput, toast } from '/@/renderer/components';
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
import { useCurrentServer } from '/@/renderer/store';
import { useTranslation } from 'react-i18next';
interface SaveAsPlaylistFormProps {
body: Partial<CreatePlaylistBody>;
@ -18,6 +19,7 @@ export const SaveAsPlaylistForm = ({
onSuccess,
onCancel,
}: SaveAsPlaylistFormProps) => {
const { t } = useTranslation();
const mutation = useCreatePlaylist({});
const server = useCurrentServer();
@ -40,10 +42,15 @@ export const SaveAsPlaylistForm = ({
{ body: values, serverId },
{
onError: (err) => {
toast.error({ message: err.message, title: 'Error creating playlist' });
toast.error({
message: err.message,
title: t('error.genericError', { postProcess: 'sentenceCase' }),
});
},
onSuccess: (data) => {
toast.success({ message: `Playlist has been created` });
toast.success({
message: t('form.createPlaylist.success', { postProcess: 'sentenceCase' }),
});
onSuccess(data);
onCancel();
},
@ -60,16 +67,25 @@ export const SaveAsPlaylistForm = ({
<TextInput
data-autofocus
required
label="Name"
label={t('form.createPlaylist.input', {
context: 'name',
postProcess: 'titleCase',
})}
{...form.getInputProps('name')}
/>
<TextInput
label="Description"
label={t('form.createPlaylist.input', {
context: 'description',
postProcess: 'titleCase',
})}
{...form.getInputProps('comment')}
/>
{isPublicDisplayed && (
<Switch
label="Is Public?"
label={t('form.createPlaylist.input', {
context: 'public',
postProcess: 'titleCase',
})}
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
/>
)}
@ -78,7 +94,7 @@ export const SaveAsPlaylistForm = ({
variant="subtle"
onClick={onCancel}
>
Cancel
{t('common.cancel', { postProcess: 'titleCase' })}
</Button>
<Button
disabled={isSubmitDisabled}
@ -86,7 +102,7 @@ export const SaveAsPlaylistForm = ({
type="submit"
variant="filled"
>
Save
{t('common.save', { postProcess: 'titleCase' })}
</Button>
</Group>
</Stack>

View file

@ -18,6 +18,8 @@ import { Button, Select, Switch, TextInput, toast } from '/@/renderer/components
import { useUpdatePlaylist } from '/@/renderer/features/playlists/mutations/update-playlist-mutation';
import { queryClient } from '/@/renderer/lib/react-query';
import { useCurrentServer } from '/@/renderer/store';
import { useTranslation } from 'react-i18next';
import i18n from '/@/i18n/i18n';
interface UpdatePlaylistFormProps {
body: Partial<UpdatePlaylistBody>;
@ -27,6 +29,7 @@ interface UpdatePlaylistFormProps {
}
export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlaylistFormProps) => {
const { t } = useTranslation();
const mutation = useUpdatePlaylist({});
const server = useCurrentServer();
@ -60,10 +63,12 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
},
{
onError: (err) => {
toast.error({ message: err.message, title: 'Error updating playlist' });
toast.error({
message: err.message,
title: t('error.genericError', { postProcess: 'sentenceCase' }),
});
},
onSuccess: () => {
toast.success({ message: `Playlist has been saved` });
onCancel();
},
},
@ -80,23 +85,35 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
<TextInput
data-autofocus
required
label="Name"
label={t('form.createPlaylist.input', {
context: 'name',
postProcess: 'titleCase',
})}
{...form.getInputProps('name')}
/>
<TextInput
label="Description"
label={t('form.createPlaylist.input', {
context: 'description',
postProcess: 'titleCase',
})}
{...form.getInputProps('comment')}
/>
{isOwnerDisplayed && (
<Select
data={userList || []}
{...form.getInputProps('_custom.navidrome.ownerId')}
label="Owner"
label={t('form.createPlaylist.input', {
context: 'owner',
postProcess: 'titleCase',
})}
/>
)}
{isPublicDisplayed && (
<Switch
label="Is Public?"
label={t('form.createPlaylist.input', {
context: 'public',
postProcess: 'titleCase',
})}
{...form.getInputProps('_custom.navidrome.public', { type: 'checkbox' })}
/>
)}
@ -105,7 +122,7 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
variant="subtle"
onClick={onCancel}
>
Cancel
{t('common.cancel', { postProcess: 'titleCase' })}
</Button>
<Button
disabled={isSubmitDisabled}
@ -113,7 +130,7 @@ export const UpdatePlaylistForm = ({ users, query, body, onCancel }: UpdatePlayl
type="submit"
variant="filled"
>
Save
{t('common.save', { postProcess: 'titleCase' })}
</Button>
</Group>
</Stack>
@ -166,6 +183,6 @@ export const openUpdatePlaylistModal = async (args: {
onCancel={closeAllModals}
/>
),
title: 'Edit playlist',
title: i18n.t('form.editPlaylist.title', { postProcess: 'titleCase' }) as string,
});
};