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,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}
/>

View file

@ -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}
/>

View file

@ -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}

View file

@ -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}
>