mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
Add localization support (#333)
* Add updated i18n config and en locale
This commit is contained in:
parent
11863fd4c1
commit
8430b1ec95
90 changed files with 2679 additions and 908 deletions
|
|
@ -1,10 +1,11 @@
|
|||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { Divider, Group, Stack } from '@mantine/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
||||
import { MultiSelect, NumberInput, Switch, Text } from '/@/renderer/components';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface JellyfinSongFiltersProps {
|
||||
customFilters?: Partial<SongListFilter>;
|
||||
|
|
@ -19,6 +20,7 @@ export const JellyfinSongFilters = ({
|
|||
onFilterChange,
|
||||
serverId,
|
||||
}: JellyfinSongFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setFilter } = useListStoreActions();
|
||||
const filter = useListFilterByKey({ key: pageKey });
|
||||
|
||||
|
|
@ -49,7 +51,7 @@ export const JellyfinSongFilters = ({
|
|||
|
||||
const toggleFilters = [
|
||||
{
|
||||
label: 'Is favorited',
|
||||
label: t('filter.isFavorited', { postProcess: 'sentenceCase' }),
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
|
|
@ -151,14 +153,14 @@ export const JellyfinSongFilters = ({
|
|||
<NumberInput
|
||||
required
|
||||
defaultValue={filter?._custom?.jellyfin?.minYear}
|
||||
label="From year"
|
||||
label={t('filter.fromYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
onChange={handleMinYearFilter}
|
||||
/>
|
||||
<NumberInput
|
||||
defaultValue={filter?._custom?.jellyfin?.maxYear}
|
||||
label="To year"
|
||||
label={t('filter.toYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
onChange={handleMaxYearFilter}
|
||||
|
|
@ -171,7 +173,7 @@ export const JellyfinSongFilters = ({
|
|||
searchable
|
||||
data={genreList}
|
||||
defaultValue={selectedGenres}
|
||||
label="Genres"
|
||||
label={t('entity.genre', { count: 1, postProcess: 'sentenceCase' })}
|
||||
width={250}
|
||||
onChange={handleGenresFilter}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { Divider, Group, Stack } from '@mantine/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
||||
import { NumberInput, Select, Switch, Text } from '/@/renderer/components';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface NavidromeSongFiltersProps {
|
||||
customFilters?: Partial<SongListFilter>;
|
||||
|
|
@ -19,6 +20,7 @@ export const NavidromeSongFilters = ({
|
|||
pageKey,
|
||||
serverId,
|
||||
}: NavidromeSongFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setFilter } = useListStoreActions();
|
||||
const filter = useListFilterByKey({ key: pageKey });
|
||||
|
||||
|
|
@ -61,7 +63,7 @@ export const NavidromeSongFilters = ({
|
|||
|
||||
const toggleFilters = [
|
||||
{
|
||||
label: 'Is favorited',
|
||||
label: t('filter.isFavorited', { postProcess: 'sentenceCase' }),
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
|
|
@ -119,7 +121,7 @@ export const NavidromeSongFilters = ({
|
|||
<Divider my="0.5rem" />
|
||||
<Group grow>
|
||||
<NumberInput
|
||||
label="Year"
|
||||
label={t('common.year', { postProcess: 'titleCase' })}
|
||||
max={5000}
|
||||
min={0}
|
||||
value={filter._custom?.navidrome?.year}
|
||||
|
|
@ -132,7 +134,7 @@ export const NavidromeSongFilters = ({
|
|||
searchable
|
||||
data={genreList}
|
||||
defaultValue={filter._custom?.navidrome?.genre_id}
|
||||
label="Genre"
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
width={150}
|
||||
onChange={handleGenresFilter}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import { Divider, Flex, Group, Stack } from '@mantine/core';
|
||||
import { openModal } from '@mantine/modals';
|
||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
RiAddBoxFill,
|
||||
RiAddCircleFill,
|
||||
|
|
@ -78,6 +79,7 @@ interface SongListHeaderFiltersProps {
|
|||
}
|
||||
|
||||
export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
const { pageKey, handlePlay, customFilters } = useListContext();
|
||||
const { display, table, filter, grid } = useListStoreByKey({
|
||||
|
|
@ -421,7 +423,7 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
fill: isFilterApplied ? 'var(--primary-color) !important' : undefined,
|
||||
},
|
||||
}}
|
||||
tooltip={{ label: 'Filters' }}
|
||||
tooltip={{ label: t('common.filters', { postProcess: 'titleCase' }) }}
|
||||
variant="subtle"
|
||||
onClick={handleOpenFiltersModal}
|
||||
>
|
||||
|
|
@ -431,7 +433,7 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
<Button
|
||||
compact
|
||||
size="md"
|
||||
tooltip={{ label: 'Refresh' }}
|
||||
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
|
||||
variant="subtle"
|
||||
onClick={handleRefresh}
|
||||
>
|
||||
|
|
@ -454,19 +456,19 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
icon={<RiPlayFill />}
|
||||
onClick={() => handlePlay?.({ playType: Play.NOW })}
|
||||
>
|
||||
Play
|
||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
icon={<RiAddBoxFill />}
|
||||
onClick={() => handlePlay?.({ playType: Play.LAST })}
|
||||
>
|
||||
Add to queue
|
||||
{t('player.addLast', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
icon={<RiAddCircleFill />}
|
||||
onClick={() => handlePlay?.({ playType: Play.NEXT })}
|
||||
>
|
||||
Add to queue next
|
||||
{t('player.addNext', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Item
|
||||
|
|
@ -496,27 +498,29 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
</Button>
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
<DropdownMenu.Label>Display type</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>
|
||||
{t('table.config.general.displayType', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.CARD}
|
||||
value={ListDisplayType.CARD}
|
||||
onClick={handleSetViewType}
|
||||
>
|
||||
Card
|
||||
{t('table.config.view.card', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.POSTER}
|
||||
value={ListDisplayType.POSTER}
|
||||
onClick={handleSetViewType}
|
||||
>
|
||||
Poster
|
||||
{t('table.config.view.poster', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.TABLE}
|
||||
value={ListDisplayType.TABLE}
|
||||
onClick={handleSetViewType}
|
||||
>
|
||||
Table
|
||||
{t('table.config.view.table', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
{/* <DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.TABLE_PAGINATED}
|
||||
|
|
@ -526,7 +530,9 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
Table (paginated)
|
||||
</DropdownMenu.Item> */}
|
||||
<DropdownMenu.Divider />
|
||||
<DropdownMenu.Label>Item Size</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>
|
||||
{t('table.config.general.size', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Item closeMenuOnClick={false}>
|
||||
<Slider
|
||||
defaultValue={isGrid ? grid?.itemSize || 0 : table.rowHeight}
|
||||
|
|
@ -537,7 +543,11 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
</DropdownMenu.Item>
|
||||
{isGrid && (
|
||||
<>
|
||||
<DropdownMenu.Label>Item gap</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>
|
||||
{t('table.config.general.gap', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Item closeMenuOnClick={false}>
|
||||
<Slider
|
||||
defaultValue={grid?.itemGap || 0}
|
||||
|
|
@ -548,7 +558,11 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
</DropdownMenu.Item>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenu.Label>Table Columns</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>
|
||||
{t('table.config.general.tableColumns', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Item
|
||||
closeMenuOnClick={false}
|
||||
component="div"
|
||||
|
|
@ -563,7 +577,11 @@ export const SongListHeaderFilters = ({ gridRef, tableRef }: SongListHeaderFilte
|
|||
onChange={handleTableColumns}
|
||||
/>
|
||||
<Group position="apart">
|
||||
<Text>Auto Fit Columns</Text>
|
||||
<Text>
|
||||
{t('table.config.general.autoFitColumns', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</Text>
|
||||
<Switch
|
||||
defaultChecked={table.autoFit}
|
||||
onChange={handleAutoFitColumns}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
|
|||
import { Flex, Group, Stack } from '@mantine/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, MutableRefObject } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useListStoreByKey } from '../../../store/list.store';
|
||||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
import { PageHeader, SearchInput } from '/@/renderer/components';
|
||||
|
|
@ -23,6 +24,7 @@ interface SongListHeaderProps {
|
|||
}
|
||||
|
||||
export const SongListHeader = ({ gridRef, title, itemCount, tableRef }: SongListHeaderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
const { pageKey, handlePlay, customFilters } = useListContext();
|
||||
const { setFilter, setTablePagination } = useListStoreActions();
|
||||
|
|
@ -71,7 +73,9 @@ export const SongListHeader = ({ gridRef, title, itemCount, tableRef }: SongList
|
|||
<LibraryHeaderBar.PlayButton
|
||||
onClick={() => handlePlay?.({ playType: playButtonBehavior })}
|
||||
/>
|
||||
<LibraryHeaderBar.Title>{title || 'Tracks'}</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Title>
|
||||
{title || t('page.trackList.title', { postProcess: 'titleCase' })}
|
||||
</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Badge
|
||||
isLoading={itemCount === null || itemCount === undefined}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue