started translations

This commit is contained in:
2025-07-23 03:28:52 +03:00
parent 562b6878d3
commit 4b9d7fd56b
32 changed files with 1104 additions and 187 deletions

View File

@@ -43,11 +43,14 @@ import {
} from '../../../state/global/notifications.ts';
import { useAtom, useSetAtom } from 'jotai';
import { editPlaylistAtom } from '../../../state/publish/playlist.ts';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 });
export const EditPlaylist = () => {
const { t } = useTranslation(['core', 'category']);
const theme = useTheme();
const { name: username, address: userAddress } = useAuth();
const setNotification = useSetAtom(setNotificationAtom);
@@ -374,9 +377,17 @@ export const EditPlaylist = () => {
}}
>
{isNew ? (
<NewCrowdfundTitle>Create new playlist</NewCrowdfundTitle>
<NewCrowdfundTitle>
{t('core:publish.create_new_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</NewCrowdfundTitle>
) : (
<NewCrowdfundTitle>Update Playlist properties</NewCrowdfundTitle>
<NewCrowdfundTitle>
{t('core:publish.update_playlist_properties', {
postProcess: 'capitalizeFirstChar',
})}
</NewCrowdfundTitle>
)}
</Box>
<>
@@ -388,16 +399,26 @@ export const EditPlaylist = () => {
}}
>
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel id="Category">Select a Category</InputLabel>
<InputLabel id="Category">
{t('core:publish.select_category', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="Category"
input={<OutlinedInput label="Select a Category" />}
input={
<OutlinedInput
label={t('core:publish.select_category', {
postProcess: 'capitalizeFirstChar',
})}
/>
}
value={selectedCategoryVideos?.id || ''}
onChange={handleOptionCategoryChangeVideos}
>
{categories.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:categories.${option.id}`)}
</MenuItem>
))}
</Select>
@@ -405,10 +426,20 @@ export const EditPlaylist = () => {
{selectedCategoryVideos &&
subCategories[selectedCategoryVideos?.id] && (
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel id="Category">Select a Sub-Category</InputLabel>
<InputLabel id="Category">
{t('core:publish.select_subcategory', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="Sub-Category"
input={<OutlinedInput label="Select a Sub-Category" />}
input={
<OutlinedInput
label={t('core:publish.select_subcategory', {
postProcess: 'capitalizeFirstChar',
})}
/>
}
value={selectedSubCategoryVideos?.id || ''}
onChange={(e) =>
handleOptionSubCategoryChangeVideos(
@@ -420,7 +451,7 @@ export const EditPlaylist = () => {
{subCategories[selectedCategoryVideos.id].map(
(option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:subcategories.${option.id}`)}
</MenuItem>
)
)}
@@ -432,7 +463,9 @@ export const EditPlaylist = () => {
{!coverImage ? (
<ImageUploader onPick={(img: string) => setCoverImage(img)}>
<AddCoverImageButton variant="contained">
Add Cover Image
{t('core:publish.add_cover_image', {
postProcess: 'capitalizeFirstChar',
})}
<AddLogoIcon
sx={{
height: '25px',
@@ -454,7 +487,9 @@ export const EditPlaylist = () => {
)}
<CustomInputField
name="title"
label="Title of playlist"
label={t('core:publish.title_playlist', {
postProcess: 'capitalizeFirstChar',
})}
variant="filled"
value={title}
onChange={(e) => {
@@ -484,7 +519,9 @@ export const EditPlaylist = () => {
fontSize: '18px',
}}
>
Description of playlist
{t('core:publish.description_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
<TextEditor
inlineContent={description}
@@ -510,7 +547,9 @@ export const EditPlaylist = () => {
variant="contained"
color="error"
>
Cancel
{t('core:action.cancel', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
<Box
sx={{
@@ -525,7 +564,9 @@ export const EditPlaylist = () => {
publishQDNResource();
}}
>
Publish
{t('core:publish.publish_action', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
</Box>
</CrowdfundActionButtonRow>

View File

@@ -22,12 +22,15 @@ import AddIcon from '@mui/icons-material/Add';
import { QTUBE_VIDEO_BASE } from '../../../constants/Identifiers.ts';
import { Spacer, useAuth } from 'qapp-core';
import { useIsSmall } from '../../../hooks/useIsSmall.tsx';
import { useTranslation } from 'react-i18next';
export const PlaylistListEdit = ({
playlistData,
updateVideoList,
removeVideo,
addVideo,
}) => {
const { t } = useTranslation(['core']);
const theme = useTheme();
const isSmall = useIsSmall();
const { name: username } = useAuth();
@@ -97,7 +100,11 @@ export const PlaylistListEdit = ({
}}
>
<CrowdfundSubTitleRow>
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
<CrowdfundSubTitle>
{t('core:publish.playlist', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundSubTitle>
</CrowdfundSubTitleRow>
<CardContentContainerComment
sx={{
@@ -186,7 +193,11 @@ export const PlaylistListEdit = ({
}}
>
<CrowdfundSubTitleRow>
<CrowdfundSubTitle>Add videos to playlist</CrowdfundSubTitle>
<CrowdfundSubTitle>
{t('core:publish.add_videos_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundSubTitle>
</CrowdfundSubTitleRow>
<CardContentContainerComment
sx={{
@@ -205,7 +216,9 @@ export const PlaylistListEdit = ({
<FormControlLabel
value="myVideos"
control={<Radio />}
label="My Videos"
label={t('core:publish.my_videos', {
postProcess: 'capitalizeFirstChar',
})}
componentsProps={{
typography: { sx: { fontSize: '14px' } },
}}
@@ -213,7 +226,9 @@ export const PlaylistListEdit = ({
<FormControlLabel
value="allVideos"
control={<Radio />}
label="All Videos"
label={t('core:publish.all_videos', {
postProcess: 'capitalizeFirstChar',
})}
componentsProps={{
typography: { sx: { fontSize: '14px' } },
}}
@@ -232,7 +247,9 @@ export const PlaylistListEdit = ({
setFilterSearch(e.target.value);
}}
value={filterSearch}
placeholder="Search by title"
placeholder={t('core:publish.search_by_title', {
postProcess: 'capitalizeFirstChar',
})}
sx={{
borderBottom: '1px solid white',
'&&:before': {
@@ -262,7 +279,9 @@ export const PlaylistListEdit = ({
}}
variant="contained"
>
Search
{t('core:navbar.search', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</Box>

View File

@@ -70,6 +70,7 @@ import {
AltertObject,
setNotificationAtom,
} from '../../../state/global/notifications.ts';
import { useTranslation } from 'react-i18next';
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
new Promise((resolve, reject) => {
@@ -105,6 +106,8 @@ export const PublishVideo = ({
editContent,
afterClose,
}: PublishVideoProps) => {
const { t } = useTranslation(['core', 'category']);
const theme = useTheme();
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
const setNotification = useSetAtom(setNotificationAtom);
@@ -658,7 +661,9 @@ export const PublishVideo = ({
setIsOpen(true);
}}
>
Video
{t('core:publish.video', {
postProcess: 'capitalizeFirstChar',
})}
</StyledButton>
)}
</>
@@ -682,9 +687,17 @@ export const PublishVideo = ({
}}
>
{editId ? (
<NewCrowdfundTitle>Update Videos</NewCrowdfundTitle>
<NewCrowdfundTitle>
{t('core:publish.update_video', {
postProcess: 'capitalizeEachFirstChar',
})}
</NewCrowdfundTitle>
) : (
<NewCrowdfundTitle>Publish Videos</NewCrowdfundTitle>
<NewCrowdfundTitle>
{t('core:publish.publish_videos', {
postProcess: 'capitalizeEachFirstChar',
})}
</NewCrowdfundTitle>
)}
</Box>
@@ -692,7 +705,9 @@ export const PublishVideo = ({
<>
<FiltersSubContainer>
<FiltersRow>
Populate Titles by filename (when the files are picked)
{t('core:publish.populate_titles', {
postProcess: 'capitalizeFirstChar',
})}
<FiltersCheckbox
checked={isCheckTitleByFile}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -702,7 +717,9 @@ export const PublishVideo = ({
/>
</FiltersRow>
<FiltersRow>
All videos use the same Cover Image
{t('core:publish.same_cover_images', {
postProcess: 'capitalizeFirstChar',
})}
<FiltersCheckbox
checked={isCheckSameCoverImage}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -712,7 +729,9 @@ export const PublishVideo = ({
/>
</FiltersRow>
<FiltersRow>
Populate all descriptions by Title
{t('core:publish.populate_description', {
postProcess: 'capitalizeFirstChar',
})}
<FiltersCheckbox
checked={isCheckDescriptionIsTitle}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -744,26 +763,37 @@ export const PublishVideo = ({
>
<input {...getInputProps()} />
<Typography>
Drag and drop a video files here or click to select files
{t('core:publish.drag_drop_videos', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box>
<Box>
<CodecTypography>
Supported File Containers:{' '}
<span style={{ fontWeight: 'bold' }}>MP4</span>, M4V, OGG,
{t('core:publish.supported_containers', {
postProcess: 'capitalizeFirstChar',
})}
: <span style={{ fontWeight: 'bold' }}>MP4</span>, M4V, OGG,
WEBM, WAV
</CodecTypography>
<CodecTypography>
Audio Codecs: <span style={{ fontWeight: 'bold' }}>Opus</span>
, MP3, FLAC, PCM (8/16/32-bit, μ-law), Vorbis
{t('core:publish.audio_codecs', {
postProcess: 'capitalizeEachFirstChar',
})}
: <span style={{ fontWeight: 'bold' }}>Opus</span>, MP3, FLAC,
PCM (8/16/32-bit, μ-law), Vorbis
</CodecTypography>
<CodecTypography>
Video Codecs: <span style={{ fontWeight: 'bold' }}>AV1</span>,
VP8, VP9, H.264
{t('core:publish.video_codecs', {
postProcess: 'capitalizeEachFirstChar',
})}
: <span style={{ fontWeight: 'bold' }}>AV1</span>, VP8, VP9,
H.264
</CodecTypography>
<CodecTypography sx={{ fontWeight: '800', color: 'red' }}>
Using unsupported Codecs may result in video or audio not
working properly
{t('core:publish.unsupported_codecs_description', {
postProcess: 'capitalizeEachFirstChar',
})}
</CodecTypography>
</Box>
@@ -777,16 +807,26 @@ export const PublishVideo = ({
{files?.length > 0 && (
<>
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel id="Category">Select a Category</InputLabel>
<InputLabel id="Category">
{t('core:publish.select_category', {
postProcess: 'capitalizeEachFirstChar',
})}
</InputLabel>
<Select
labelId="Category"
input={<OutlinedInput label="Select a Category" />}
input={
<OutlinedInput
label={t('core:publish.select_category', {
postProcess: 'capitalizeEachFirstChar',
})}
/>
}
value={selectedCategoryVideos?.id || ''}
onChange={handleOptionCategoryChangeVideos}
>
{categories.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:categories.${option.id}`)}
</MenuItem>
))}
</Select>
@@ -795,12 +835,18 @@ export const PublishVideo = ({
subCategories[selectedCategoryVideos?.id] && (
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel id="Category">
Select a Sub-Category
{t('core:publish.select_subcategory', {
postProcess: 'capitalizeEachFirstChar',
})}
</InputLabel>
<Select
labelId="Sub-Category"
input={
<OutlinedInput label="Select a Sub-Category" />
<OutlinedInput
label={t('core:publish.select_subcategory', {
postProcess: 'capitalizeEachFirstChar',
})}
/>
}
value={selectedSubCategoryVideos?.id || ''}
onChange={(e) =>
@@ -813,7 +859,7 @@ export const PublishVideo = ({
{subCategories[selectedCategoryVideos.id].map(
(option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:subcategories.${option.id}`)}
</MenuItem>
)
)}
@@ -830,7 +876,9 @@ export const PublishVideo = ({
onPick={(img: string) => setCoverImageForAll(img)}
>
<AddCoverImageButton variant="contained">
Add Cover Image
{t('core:publish.add_cover_image', {
postProcess: 'capitalizeEachFirstChar',
})}
<AddLogoIcon
sx={{
height: '25px',
@@ -875,7 +923,9 @@ export const PublishVideo = ({
}
>
<AddCoverImageButton variant="contained">
Add Cover Image
{t('core:publish.add_cover_image', {
postProcess: 'capitalizeEachFirstChar',
})}
<AddLogoIcon
sx={{
height: '25px',
@@ -921,7 +971,9 @@ export const PublishVideo = ({
fontSize: '18px',
}}
>
Description of video
{t('core:publish.description_video', {
postProcess: 'capitalizeEachFirstChar',
})}
</Typography>
<TextEditor
inlineContent={file?.description}
@@ -945,7 +997,11 @@ export const PublishVideo = ({
display: 'flex',
}}
>
<Typography>Playlist</Typography>
<Typography>
{t('core:publish.playlist', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box>
<Box
sx={{
@@ -963,14 +1019,18 @@ export const PublishVideo = ({
marginTop: '20px',
}}
>
Would you like to add these videos to a playlist?
{t('core:publish.add_vids_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
<Typography
sx={{
fontSize: '16px',
}}
>
Add to a playlist is OPTIONAL
{t('core:publish.add_vids_playlist_optional', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box>
<Box
@@ -988,7 +1048,9 @@ export const PublishVideo = ({
setPlaylistSetting(null);
}}
>
no playlist
{t('core:publish.no_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
<CrowdfundActionButton
color={playlistSetting === 'new' ? 'success' : 'primary'}
@@ -997,7 +1059,9 @@ export const PublishVideo = ({
setPlaylistSetting('new');
}}
>
New playlist
{t('core:publish.new_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
<CrowdfundActionButton
color={playlistSetting === 'existing' ? 'success' : 'primary'}
@@ -1006,7 +1070,9 @@ export const PublishVideo = ({
setPlaylistSetting('existing');
}}
>
Existing playlist
{t('core:publish.existing_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
</Box>
{playlistSetting === 'existing' && (
@@ -1020,7 +1086,9 @@ export const PublishVideo = ({
>
<CrowdfundSubTitleRow>
<CrowdfundSubTitle>
Select existing playlist
{t('core:publish.select_existing_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundSubTitle>
</CrowdfundSubTitleRow>
<Typography>
@@ -1048,7 +1116,12 @@ export const PublishVideo = ({
setFilterSearch(e.target.value);
}}
value={filterSearch}
placeholder="Search by title"
placeholder={t(
'core:publish.playlist_search_by_title',
{
postProcess: 'capitalizeFirstChar',
}
)}
sx={{
borderBottom: '1px solid white',
'&&:before': {
@@ -1075,7 +1148,9 @@ export const PublishVideo = ({
}}
variant="contained"
>
Search
{t('core:navbar.search', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</Box>
@@ -1129,7 +1204,9 @@ export const PublishVideo = ({
onPick={(img: string) => setPlaylistCoverImage(img)}
>
<AddCoverImageButton variant="contained">
Add Cover Image
{t('core:publish.add_cover_image', {
postProcess: 'capitalizeFirstChar',
})}
<AddLogoIcon
sx={{
height: '25px',
@@ -1151,7 +1228,9 @@ export const PublishVideo = ({
)}
<CustomInputField
name="title"
label="Title of playlist"
label={t('core:publish.title_playlist', {
postProcess: 'capitalizeFirstChar',
})}
variant="filled"
value={playlistTitle}
onChange={(e) => {
@@ -1171,7 +1250,9 @@ export const PublishVideo = ({
fontSize: '18px',
}}
>
Description of playlist
{t('core:publish.description_playlist', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
<TextEditor
inlineContent={playlistDescription}
@@ -1180,16 +1261,26 @@ export const PublishVideo = ({
}}
/>
<FormControl fullWidth sx={{ marginBottom: 2, marginTop: 2 }}>
<InputLabel id="Category">Select a Category</InputLabel>
<InputLabel id="Category">
{t('core:publish.select_category', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="Category"
input={<OutlinedInput label="Select a Category" />}
input={
<OutlinedInput
label={t('core:publish.select_category', {
postProcess: 'capitalizeFirstChar',
})}
/>
}
value={selectedCategory?.id || ''}
onChange={handleOptionCategoryChange}
>
{categories.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:categories.${option.id}`)}
</MenuItem>
))}
</Select>
@@ -1197,11 +1288,19 @@ export const PublishVideo = ({
{selectedCategory && subCategories[selectedCategory?.id] && (
<FormControl fullWidth sx={{ marginBottom: 2 }}>
<InputLabel id="Category">
Select a Sub-Category
{t('core:publish.select_subcategory', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="Sub-Category"
input={<OutlinedInput label="Select a Sub-Category" />}
input={
<OutlinedInput
label={t('core:publish.select_subcategory', {
postProcess: 'capitalizeFirstChar',
})}
/>
}
value={selectedSubCategory?.id || ''}
onChange={(e) =>
handleOptionSubCategoryChange(
@@ -1212,7 +1311,7 @@ export const PublishVideo = ({
>
{subCategories[selectedCategory.id].map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:subcategories.${option.id}`)}
</MenuItem>
))}
</Select>
@@ -1247,7 +1346,9 @@ export const PublishVideo = ({
setStep('videos');
}}
>
Back
{t('core:action.back', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
)}
{step === 'playlist' ? (
@@ -1257,7 +1358,9 @@ export const PublishVideo = ({
publishQDNResource();
}}
>
Publish
{t('core:publish.publish_action', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
) : (
<CrowdfundActionButton
@@ -1270,12 +1373,16 @@ export const PublishVideo = ({
}}
>
{files?.length !== Object.keys(imageExtracts)?.length
? 'Generating image extracts'
? t('core:publish.generationg_extracts', {
postProcess: 'capitalizeFirstChar',
})
: ''}
{files?.length !== Object.keys(imageExtracts)?.length && (
<CircularProgress color="secondary" size={14} />
)}
Next
{t('core:publish.next', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
)}
</Box>

View File

@@ -35,6 +35,7 @@ import Portal from '../Portal';
import { formatDate } from '../../../utils/time';
import { createAvatarLink, Spacer, useAuth } from 'qapp-core';
import { useIsSmall } from '../../../hooks/useIsSmall';
import { useTranslation } from 'react-i18next';
interface CommentProps {
comment: any;
postId: string;
@@ -47,6 +48,8 @@ export const Comment = ({
postName,
onSubmit,
}: CommentProps) => {
const { t } = useTranslation(['core']);
const isSmall = useIsSmall();
const [isReplying, setIsReplying] = useState<boolean>(false);
const [isEditing, setIsEditing] = useState<boolean>(false);
@@ -98,7 +101,9 @@ export const Comment = ({
</DialogContent>
<DialogActions>
<Button variant="contained" onClick={() => setCurrentEdit(null)}>
Close
{t('core:action.close', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</DialogActions>
</Dialog>
@@ -151,7 +156,12 @@ export const Comment = ({
>
{!isReplying && (
<ButtonBase onClick={() => setIsReplying(true)}>
<Typography>Reply</Typography>
<Typography>
{' '}
{t('core:action.reply', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</ButtonBase>
)}
@@ -195,8 +205,12 @@ export const Comment = ({
}}
>
{isOpenReplies
? ` Hide all replies (${comment?.replies?.length})`
: ` View all replies (${comment?.replies?.length})`}
? ` ${t('core:comments.hide_replies', {
postProcess: 'capitalizeFirstChar',
})} (${comment?.replies?.length})`
: ` ${t('core:comments.view_replies', {
postProcess: 'capitalizeFirstChar',
})} (${comment?.replies?.length})`}
</Typography>
</ButtonBase>
)}

View File

@@ -17,6 +17,7 @@ import {
} from '../../../state/global/notifications.ts';
import { Box, Button } from '@mui/material';
import { useIsSmall } from '../../../hooks/useIsSmall.tsx';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 7 });
const notification = localforage.createInstance({
@@ -115,6 +116,8 @@ export const CommentEditor = ({
commentMessage,
onCloseReply,
}: CommentEditorProps) => {
const { t } = useTranslation(['core']);
const isSmall = useIsSmall();
const [value, setValue] = useState<string>('');
const { name, address } = useAuth();
@@ -262,14 +265,26 @@ export const CommentEditor = ({
}}
variant="text"
>
Cancel
{t('core:action.cancel', {
postProcess: 'capitalizeEachFirstChar',
})}
</Button>
<SubmitCommentButton
variant="contained"
color="info"
onClick={handleSubmit}
>
{isReply ? 'Submit reply' : isEdit ? 'Edit' : 'comment'}
{isReply
? t('core:comments.submit_reply', {
postProcess: 'capitalizeFirstChar',
})
: isEdit
? t('core:action.edit', {
postProcess: 'capitalizeFirstChar',
})
: t('core:action.comment', {
postProcess: 'capitalizeFirstChar',
})}
</SubmitCommentButton>
</Box>
</CommentInputContainer>

View File

@@ -18,6 +18,7 @@ import {
} from '../../Publish/PublishVideo/PublishVideo-styles.tsx';
import { COMMENT_BASE } from '../../../constants/Identifiers.ts';
import { hashWordWithoutPublicSalt } from 'qapp-core';
import { useTranslation } from 'react-i18next';
interface CommentSectionProps {
postId: string;
@@ -49,6 +50,8 @@ const Panel = styled('div')`
}
`;
export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
const { t } = useTranslation(['core']);
const navigate = useNavigate();
const location = useLocation();
const [listComments, setListComments] = useState<any[]>([]);
@@ -262,7 +265,9 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
variant="contained"
size="small"
>
Load More Comments
{t('core:comments.load_more_comments', {
postProcess: 'capitalizeEachFirstChar',
})}
</LoadMoreCommentsButton>
</LoadMoreCommentsButtonRow>
)}

View File

@@ -19,10 +19,13 @@ import { InstallDesktopRounded } from '@mui/icons-material';
import { usePersistedState } from '../../../state/persist/persist';
import ShortUniqueId from 'short-unique-id';
import { Spacer, useGlobal } from 'qapp-core';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 15, dictionary: 'alphanum' });
export const AddToBookmarks = ({ metadataReference }) => {
const { t } = useTranslation(['core']);
const { lists: globalLists } = useGlobal();
const [bookmarks, setBookmarks, isHydratedSubscriptions] = usePersistedState(
'bookmarks-v1',
@@ -247,7 +250,9 @@ export const AddToBookmarks = ({ metadataReference }) => {
fontSize: '0.85rem',
}}
>
Bookmark lists
{t('core:bookmarks.bookmarks_lists', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
<CloseIcon
@@ -295,7 +300,9 @@ export const AddToBookmarks = ({ metadataReference }) => {
marginTop: '20px',
}}
>
No bookmark lists
{t('core:bookmarks.no_bookmarks_lists', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
)}
<Spacer height="10px" />
@@ -341,7 +348,9 @@ export const AddToBookmarks = ({ metadataReference }) => {
variant="contained"
size="small"
>
New list
{t('core:bookmarks.new_list', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</Box>
</>
@@ -380,7 +389,9 @@ export const AddToBookmarks = ({ metadataReference }) => {
fontSize: '0.85rem',
}}
>
Bookmark lists
{t('core:bookmarks.bookmarks_lists', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</ButtonBase>
</Box>
@@ -432,7 +443,9 @@ export const AddToBookmarks = ({ metadataReference }) => {
variant="contained"
size="small"
>
Cancel
{t('core:action.cancel', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
<Button
onClick={handleCreateList}
@@ -440,7 +453,9 @@ export const AddToBookmarks = ({ metadataReference }) => {
variant="contained"
size="small"
>
Create
{t('core:action.create', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</Box>
</>

View File

@@ -3,6 +3,7 @@ import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
import { MouseEvent, useEffect, useState } from 'react';
import { darken, styled } from '@mui/material/styles';
import { CustomTooltip, TooltipLine } from './CustomTooltip.tsx';
import { useTranslation } from 'react-i18next';
interface FollowButtonProps extends ButtonProps {
followerName: string;
@@ -14,6 +15,8 @@ export type FollowData = {
};
export const FollowButton = ({ followerName, ...props }: FollowButtonProps) => {
const { t } = useTranslation(['core']);
const [followingList, setFollowingList] = useState<string[]>([]);
const [followingSize, setFollowingSize] = useState<string>('');
const [followingItemCount, setFollowingItemCount] = useState<string>('');
@@ -118,13 +121,25 @@ export const FollowButton = ({ followerName, ...props }: FollowButtonProps) => {
const tooltipTitle = followingSize && (
<>
<TooltipLine>
Following a name automatically downloads all of its content to your
node. The more followers a name has, the faster its content will
download for everyone.
{t('core:video.follow_description', {
postProcess: 'capitalizeFirstChar',
})}
</TooltipLine>
<br />
<TooltipLine>{`${followerName}'s Current Download Size: ${followingSize}`}</TooltipLine>
<TooltipLine>{`Number of Files: ${followingItemCount}`}</TooltipLine>
<TooltipLine>
{' '}
{t('core:downloads.currentSize', {
followerName,
followingSize,
postProcess: 'capitalizeFirstChar',
})}
</TooltipLine>
<TooltipLine>
{t('core:downloads.itemCount', {
followingItemCount,
postProcess: 'capitalizeFirstChar',
})}
</TooltipLine>
</>
);
@@ -145,7 +160,13 @@ export const FollowButton = ({ followerName, ...props }: FollowButtonProps) => {
};
}}
>
{isFollowingName() ? 'Unfollow' : 'Follow'}
{isFollowingName()
? t('core:action.unfollow', {
postProcess: 'capitalizeFirstChar',
})
: t('core:action.follow', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</CustomTooltip>
</>

View File

@@ -19,6 +19,7 @@ import {
AltertObject,
setNotificationAtom,
} from '../../../state/global/notifications.ts';
import { useTranslation } from 'react-i18next';
interface LikeAndDislikeProps {
name: string;
@@ -33,6 +34,8 @@ export const LIKE = LikeType.Like;
export const DISLIKE = LikeType.Dislike;
export const NEUTRAL = LikeType.Neutral;
export const LikeAndDislike = ({ name, identifier }: LikeAndDislikeProps) => {
const { t } = useTranslation(['core']);
const { name: username } = useAuth();
const [likeCount, setLikeCount] = useState<number>(0);
const [dislikeCount, setDislikeCount] = useState<number>(0);
@@ -158,7 +161,12 @@ export const LikeAndDislike = ({ name, identifier }: LikeAndDislikeProps) => {
flexShrink: 0,
}}
>
<CustomTooltip title="Like or Dislike Video" placement="top">
<CustomTooltip
title={t('core:likes.like_dislike', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<Box
sx={{
padding: '5px',

View File

@@ -4,6 +4,7 @@ import { MouseEvent, useMemo } from 'react';
import { CustomTooltip, TooltipLine } from './CustomTooltip.tsx';
import { useAuth, useGlobal } from 'qapp-core';
import { usePersistedState } from '../../../state/persist/persist.ts';
import { useTranslation } from 'react-i18next';
interface SubscribeButtonProps extends ButtonProps {
subscriberName: string;
@@ -18,6 +19,8 @@ export const SubscribeButton = ({
subscriberName,
...props
}: SubscribeButtonProps) => {
const { t } = useTranslation(['core']);
const { lists } = useGlobal();
const [subscriptions, setSubscriptions, isHydratedSubscriptions] =
usePersistedState('subscriptions', []);
@@ -71,8 +74,9 @@ export const SubscribeButton = ({
const tooltipTitle = (
<>
<TooltipLine>
Subscribing to a name lets you see their content on the Subscriptions
tab of the Home Page. This does NOT download any data to your node.
{t('core:video.subscribe_description', {
postProcess: 'capitalizeFirstChar',
})}
</TooltipLine>
</>
);
@@ -95,7 +99,13 @@ export const SubscribeButton = ({
};
}}
>
{isSubscribed ? 'Unsubscribe' : 'Subscribe'}
{isSubscribed
? t('core:action.unsubscribe', {
postProcess: 'capitalizeFirstChar',
})
: t('core:video.subscribe', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</CustomTooltip>
);

View File

@@ -42,6 +42,7 @@ import {
AltertObject,
setNotificationAtom,
} from '../../../state/global/notifications.ts';
import { useTranslation } from 'react-i18next';
const uid = new ShortUniqueId({ length: 7 });
@@ -53,6 +54,8 @@ export const SuperLike = ({
totalAmount,
numberOfSuperlikes,
}) => {
const { t } = useTranslation(['core']);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [superlikeDonationAmount, setSuperlikeDonationAmount] =
@@ -202,7 +205,12 @@ export const SuperLike = ({
flexShrink: 0,
}}
>
<CustomTooltip title="Super Like" placement="top">
<CustomTooltip
title={t('core:likes.super_like', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<Box
sx={{
display: 'flex',
@@ -266,7 +274,11 @@ export const SuperLike = ({
width: '100%',
}}
>
<NewCrowdfundTitle>Super Like</NewCrowdfundTitle>
<NewCrowdfundTitle>
{t('core:likes.super_like', {
postProcess: 'capitalizeFirstChar',
})}
</NewCrowdfundTitle>
</Box>
<DialogContent sx={{ padding: '10px 12px' }}>
<Box>
@@ -274,7 +286,9 @@ export const SuperLike = ({
sx={{ color: 'white' }}
htmlFor="standard-adornment-amount"
>
Amount
{t('core:payments.amount', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<BoundedNumericTextField
addIconButtons={!isScreenSmall}
@@ -322,7 +336,9 @@ export const SuperLike = ({
<CommentInput
id="standard-multiline-flexible"
label="Comment Here"
label={t('core:comments.comment_here', {
postProcess: 'capitalizeFirstChar',
})}
multiline
minRows={8}
maxRows={8}
@@ -359,7 +375,9 @@ export const SuperLike = ({
variant="contained"
color="error"
>
Cancel
{t('core:action.cancel', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
<CrowdfundActionButton
@@ -368,7 +386,9 @@ export const SuperLike = ({
publishSuperLike();
}}
>
Publish
{t('core:publish.publish_action', {
postProcess: 'capitalizeFirstChar',
})}
</CrowdfundActionButton>
</Box>
</CrowdfundActionButtonRow>

View File

@@ -16,7 +16,10 @@ import { Spacer } from 'qapp-core';
import { AnimatePresence, motion } from 'framer-motion';
import { CustomChip } from '../../../pages/Home/FilterOptions.tsx';
import { useIsSmall } from '../../../hooks/useIsSmall.tsx';
import { useTranslation } from 'react-i18next';
export const ListSuperLikeContainer = ({ from }) => {
const { t } = useTranslation(['core']);
const [superlikelist] = useAtom(superlikesAtom);
const isSmall = useIsSmall();
const headerSX = { color: 'gold' };
@@ -55,7 +58,11 @@ export const ListSuperLikeContainer = ({ from }) => {
overflow: 'hidden',
}}
>
<Typography sx={headerSX}>Recent Superlikes</Typography>
<Typography sx={headerSX}>
{t('core:likes.recent_super_likes', {
postProcess: 'capitalizeEachFirstChar',
})}
</Typography>
<Spacer height="10px" />
<ListSuperLikes superlikes={superlikelist} />
</motion.div>
@@ -115,7 +122,12 @@ export const ListSuperLikeContainer = ({ from }) => {
padding: '10px',
}}
>
<Typography sx={headerSX}>Recent Superlikes</Typography>
<Typography sx={headerSX}>
{' '}
{t('core:likes.recent_super_likes', {
postProcess: 'capitalizeEachFirstChar',
})}
</Typography>
<CrowdfundActionButton
variant="contained"
color="error"

View File

@@ -34,6 +34,7 @@ import {
} from '../../../constants/Identifiers.ts';
import { minPriceSuperLike } from '../../../constants/Misc.ts';
import { useAuth } from 'qapp-core';
import { useTranslation } from 'react-i18next';
const generalLocal = localForage.createInstance({
name: 'q-tube-general',
@@ -64,6 +65,8 @@ export function extractIdValue(metadescription) {
}
export const Notifications = () => {
const { t } = useTranslation(['core']);
const [anchorElNotification, setAnchorElNotification] =
useState<HTMLButtonElement | null>(null);
const [notifications, setNotifications] = useState<any[]>([]);
@@ -280,7 +283,11 @@ export const Notifications = () => {
>
{fullNotifications.length === 0 && (
<ListItem>
<ListItemText primary="No new notifications"></ListItemText>
<ListItemText
primary={t('core:notification.no_new_notification', {
postProcess: 'capitalizeFirstChar',
})}
></ListItemText>
</ListItem>
)}
{fullNotifications.map((notification: any, index: number) => (
@@ -312,7 +319,9 @@ export const Notifications = () => {
variant="body1"
color="textPrimary"
>
Super Like
{t('core:likes.super_like', {
postProcess: 'capitalizeEachFirstChar',
})}
</Typography>
<ThumbUpIcon
style={{
@@ -339,7 +348,9 @@ export const Notifications = () => {
}}
color="textSecondary"
>
{` from ${notification.name}`}
{` ${t('core:notification.from_user', {
user: notification.name,
})}`}
</Typography>
</React.Fragment>
}

View File

@@ -19,8 +19,11 @@ import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
import { editPlaylistAtom } from '../../../../state/publish/playlist.ts';
import { useSetAtom } from 'jotai';
import { useIsSmall } from '../../../../hooks/useIsSmall.tsx';
import { useTranslation } from 'react-i18next';
export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
const { t } = useTranslation(['core']);
const popMenuRef = useRef<PopMenuRefType>(null);
const setEditPlaylist = useSetAtom(editPlaylistAtom);
const isSmall = useIsSmall();
@@ -38,7 +41,9 @@ export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
color="info"
variant="contained"
>
Publish
{t('core:publish.publish_action', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
) : (
<ButtonBase>
@@ -88,7 +93,9 @@ export const PublishMenu = ({ isDisplayed }: PublishButtonsProps) => {
setEditPlaylist({ mode: 'new' });
}}
>
Playlist
{t('core:publish.playlist', {
postProcess: 'capitalizeFirstChar',
})}
</StyledButton>
</DropdownContainer>
</PopMenu>

View File

@@ -15,6 +15,7 @@ import { UserDropDown } from '../../../UserDropDown.tsx';
import { Names } from '../../../../state/global/names.ts';
import { useAuth } from 'qapp-core';
import { useIsSmall } from '../../../../hooks/useIsSmall.tsx';
import { useTranslation } from 'react-i18next';
export interface NavBarMenuProps {
isShowMenu: boolean;
userAvatar: string | null;
@@ -28,6 +29,8 @@ export const UserMenu = ({
userName,
allNames,
}: NavBarMenuProps) => {
const { t } = useTranslation(['core']);
const isSmall = useIsSmall();
const theme = useTheme();
const { switchName } = useAuth();
@@ -58,7 +61,6 @@ export const UserMenu = ({
<AvatarContainer
sx={{
height: isSmall ? '35px' : '40px',
// width: isSmall ? '35px' : '40px',
}}
>
<Avatar
@@ -95,7 +97,12 @@ export const UserMenu = ({
height: menuIconSize,
}}
/>
<DropdownText>Blocked Names</DropdownText>
<DropdownText>
{' '}
{t('core:blocked.blocked_names', {
postProcess: 'capitalizeEachFirstChar',
})}
</DropdownText>
</DropdownContainer>
</PopMenu>
{isOpenBlockedNamesModal && (

View File

@@ -19,6 +19,7 @@ import ClearAllIcon from '@mui/icons-material/ClearAll';
import { usePersistedState } from '../../../state/persist/persist';
import { useIsSmall } from '../../../hooks/useIsSmall';
import { AnimatePresence, motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
const SearchParent = styled('div')(({ theme }) => ({
position: 'relative',
@@ -51,6 +52,8 @@ const StyledInputBase = styled(InputBase)(({ theme }) => ({
}));
export const Search = () => {
const { t } = useTranslation(['core']);
const isSmall = useIsSmall();
const navigate = useNavigate();
const searchWrapperRef = useRef<HTMLDivElement>(null);
@@ -377,7 +380,9 @@ export const Search = () => {
}}
>
<ClearAllIcon fontSize="small" />
Clear search history
{t('core:navbar.clear_history', {
postProcess: 'capitalizeFirstChar',
})}
</Box>
{searchHistory.map((term, index) => (
<Box
@@ -419,7 +424,7 @@ export const Search = () => {
<StyledInputBase
inputRef={inputRef}
size="small"
placeholder="Search…"
placeholder={`${t('core:navbar.search', { postProcess: 'capitalizeFirstChar' })}...`}
inputProps={{ 'aria-label': 'search' }}
value={filterSearch}
onChange={(e) => setFilterSearch(e.target.value)}

View File

@@ -26,10 +26,13 @@ import { Spacer, useAuth } from 'qapp-core';
import { useIsSmall } from '../../../hooks/useIsSmall';
import { UserMenu } from '../Navbar/Components/UserMenu';
import { DownloadTaskManager } from '../../common/DownloadTaskManager';
import { useTranslation } from 'react-i18next';
const DRAWER_WIDTH = 240;
export const COLLAPSED_WIDTH = 68;
export const Sidenav = ({ allNames }) => {
const { t } = useTranslation(['core']);
const isSmall = useIsSmall();
const navigate = useNavigate();
const location = useLocation();
@@ -42,39 +45,51 @@ export const Sidenav = ({ allNames }) => {
const drawerItems = useMemo(() => {
return [
{
name: 'Home',
name: t('core:sidenav.home', {
postProcess: 'capitalizeFirstChar',
}),
icon: HomeIcon,
path: '/',
},
{
name: 'Subscriptions',
name: t('core:sidenav.subscriptions', {
postProcess: 'capitalizeFirstChar',
}),
icon: StarIcon,
path: '/subscriptions',
},
{
name: 'Watched videos',
name: t('core:sidenav.watched_videos', {
postProcess: 'capitalizeFirstChar',
}),
icon: AccessTimeIcon,
path: '/history',
},
{
name: 'Bookmarks',
name: t('core:sidenav.bookmarks', {
postProcess: 'capitalizeFirstChar',
}),
icon: BookmarksIcon,
path: '/bookmarks',
},
{
name: 'Your playlists',
name: t('core:sidenav.your_playlists', {
postProcess: 'capitalizeFirstChar',
}),
icon: PlaylistPlayIcon,
path: `/channel/${name}/playlists`,
disabled: !name,
},
{
name: 'Your videos',
name: t('core:sidenav.your_videos', {
postProcess: 'capitalizeFirstChar',
}),
icon: PlayCircleOutlineIcon,
path: `/channel/${name}/videos`,
disabled: !name,
},
];
}, [name]);
}, [name, t]);
return (
<>
@@ -84,6 +99,7 @@ export const Sidenav = ({ allNames }) => {
sx={{
flexShrink: 0,
whiteSpace: 'nowrap',
overflow: 'hidden',
'& .MuiDrawer-paper': {
width: COLLAPSED_WIDTH,
position: 'relative', // key change
@@ -100,14 +116,18 @@ export const Sidenav = ({ allNames }) => {
}}
open
>
<List>
<List
sx={{
overflow: 'hidden',
}}
>
{drawerItems.map((item, index) => {
const isSelected = location.pathname === item.path;
return (
<ListItem
key={item.name}
disablePadding
sx={{ display: 'block', padding: '5px' }}
sx={{ display: 'block', padding: '5px', overflow: 'hidden' }}
>
<ListItemButton
disabled={item.disabled}
@@ -141,7 +161,12 @@ export const Sidenav = ({ allNames }) => {
}}
/>
</ListItemIcon>
<ListItemText primary={item.name} sx={{ opacity: 0 }} />
<ListItemText
primary={item.name}
sx={{
opacity: 0,
}}
/>
</ListItemButton>
</ListItem>
);
@@ -159,6 +184,7 @@ export const Sidenav = ({ allNames }) => {
height: '100vh',
width: '100vw',
display: isSideBarExpanded ? 'block' : 'none',
overflow: 'hidden',
}}
onClick={() => {
if (isSideBarExpanded) {
@@ -180,9 +206,12 @@ export const Sidenav = ({ allNames }) => {
sx={{
flexShrink: 0,
whiteSpace: 'nowrap',
overflow: 'hidden',
transition: 'opacity 0.3s',
'& .MuiDrawer-paper': {
width: isSideBarExpanded ? DRAWER_WIDTH : 0,
transition: 'all 0.3s',
width: DRAWER_WIDTH,
left: isSideBarExpanded ? 0 : -100000,
transition: 'opacity 0.3s',
opacity: isSideBarExpanded ? 1 : 0,
position: 'fixed', // key change
zIndex: 1200,
@@ -190,11 +219,16 @@ export const Sidenav = ({ allNames }) => {
bottom: 0,
bgcolor: 'background.default',
borderRight: 'none',
overFlow: 'hidden',
},
}}
open
>
<List>
<List
sx={{
overflow: 'hidden',
}}
>
{isSmall && (
<>
<ListItem
@@ -239,7 +273,11 @@ export const Sidenav = ({ allNames }) => {
}}
>
<DownloadTaskManager />
<Typography>Downloads</Typography>
<Typography>
{t('core:sidenav.downloads', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box>
</ListItem>
<Divider />
@@ -252,7 +290,11 @@ export const Sidenav = ({ allNames }) => {
<ListItem
key={item.name}
disablePadding
sx={{ display: 'block', padding: '5px' }}
sx={{
display: 'block',
padding: '5px',
overflow: 'hidden',
}}
>
<ListItemButton
onClick={() => {
@@ -292,7 +334,12 @@ export const Sidenav = ({ allNames }) => {
</ListItemIcon>
<ListItemText
primary={item.name}
sx={{ opacity: isSideBarExpanded ? 1 : 0 }}
sx={{
opacity: isSideBarExpanded ? 1 : 0,
whiteSpace: 'normal',
wordBreak: 'break-word',
overflow: 'hidden',
}}
/>
</ListItemButton>
</ListItem>

View File

@@ -2,6 +2,7 @@ import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import {
capitalizeAll,
capitalizeEachFirstChar,
capitalizeFirstChar,
capitalizeFirstWord,
} from './processors';
@@ -41,6 +42,7 @@ i18n
.use(capitalizeAll as any)
.use(capitalizeFirstChar as any)
.use(capitalizeFirstWord as any)
.use(capitalizeEachFirstChar as any)
.init({
resources,
fallbackLng: 'en',

View File

@@ -0,0 +1,89 @@
{
"categories": {
"1": "Movies",
"2": "Series",
"3": "Music",
"4": "Education",
"5": "Lifestyle",
"6": "Gaming",
"7": "Technology",
"8": "Sports",
"9": "News & Politics",
"10": "Cooking & Food",
"11": "Animation",
"12": "Science",
"13": "Health & Wellness",
"14": "DIY & Crafts",
"15": "Kids & Family",
"16": "Comedy",
"17": "Travel & Adventure",
"18": "Art & Design",
"19": "Nature & Environment",
"20": "Business & Finance",
"21": "Personal Development",
"22": "Religion",
"23": "History",
"24": "Anime",
"25": "Cartoons",
"26": "Qortal",
"27": "Paranormal",
"28": "Spirituality",
"29": "Privacy",
"99": "Other"
},
"subcategories": {
"101": "Action & Adventure",
"102": "Comedy",
"103": "Drama",
"104": "Fantasy & Science Fiction",
"105": "Horror & Thriller",
"106": "Documentaries",
"107": "Animated",
"108": "Family & Kids",
"109": "Romance",
"110": "Mystery & Crime",
"111": "Historical & War",
"112": "Musicals & Music Films",
"113": "Indie Films",
"114": "International Films",
"115": "Biographies & True Stories",
"199": "Other",
"201": "Dramas",
"202": "Comedies",
"203": "Reality & Competition",
"204": "Documentaries & Docuseries",
"205": "Sci-Fi & Fantasy",
"206": "Crime & Mystery",
"207": "Animated Series",
"208": "Kids & Family",
"209": "Historical & Period Pieces",
"210": "Action & Adventure",
"211": "Horror & Thriller",
"212": "Romance",
"213": "Anthologies",
"214": "International Series",
"215": "Miniseries",
"299": "Other",
"400": "Tutorial",
"401": "Qortal",
"402": "Documentary",
"499": "Other",
"2401": "Kodomomuke",
"2402": "Shonen",
"2403": "Shoujo",
"2404": "Seinen",
"2405": "Josei",
"2406": "Mecha",
"2407": "Mahou Shoujo",
"2408": "Isekai",
"2409": "Yaoi",
"2410": "Yuri",
"2411": "Harem",
"2412": "Ecchi",
"2413": "Idol",
"2499": "Other"
}
}

View File

@@ -1,4 +1,126 @@
{
"using_theme": "this application is using the theme:",
"welcome": "welcome to Qortal"
{
"action": {
"back": "back",
"next": "next",
"cancel": "cancel",
"delete": "delete",
"close": "close",
"save": "save",
"subscribe": "subscribe",
"unsubscribe": "unsubscribe",
"follow": "follow",
"unfollow": "unfollow",
"create": "create",
"edit": "edit",
"comment": "comment",
"reply": "reply"
},
"navbar": {
"search": "search"
},
"publish": {
"publish_action": "publish",
"video": "video",
"playlist": "playlist",
"downloads": "downloads",
"update_video": "update video",
"publish_videos": "publish videos",
"populate_titles": "Populate Titles by filename (when the files are picked)",
"same_cover_images": "All videos use the same Cover Image",
"populate_description": "Populate all descriptions by Title",
"drag_drop_videos": "Drag and drop a video files here or click to select files",
"supported_containers": "Supported File Containers",
"audio_codecs": "audio codecs",
"video_codecs": "video codecs",
"unsupported_codecs_description": "Using unsupported Codecs may result in video or audio notworking properly",
"select_category": "Select a Category",
"select_subcategory": "Select a Sub-Category",
"add_cover_image": "add cover image",
"description_video": "description of video",
"add_vids_playlist": "would you like to add these videos to a playlist?",
"add_vids_playlist_optional": "add to a playlist is OPTIONAL",
"no_playlist": "no playlist",
"new_playlist": "new playlist",
"existing_playlist": "existing playlist",
"select_existing_playlist": "select existing playlist",
"playlist_search_by_title": "search by title",
"title_playlist": "title of playlist",
"description_playlist": "description of playlist",
"generationg_extracts": "generating image extracts",
"create_new_playlist": "create new playlist",
"update_playlist_properties": "update playlist properties",
"add_videos_playlist": "add videos to playlist",
"my_videos": "my videos",
"all_videos": "all videos",
"search_by_title": "search by title",
"edit_playlist": "edit playlist",
"block_user_content": "block user content",
"edit_video": "edit video properties",
"delete_video": "delete video",
"remove_video_list": "remove video from list",
"playlist_not_exist": "this playlist does not exist"
},
"notification": {
"no_new_notification": "no new notifications",
"from_user": "from {{user}}"
},
"likes": {
"super_like": "super like",
"recent_super_likes": "recent super likes",
"like_dislike": "Like or Dislike Video"
},
"blocked": {
"blocked_names": "blocked names"
},
"sidenav": {
"home": "home",
"subscriptions": "subscriptions",
"watched_videos": "watched videos",
"bookmarks": "bookmarks",
"your_playlists": "your playlists",
"your_videos": "your videos"
},
"filters": {
"most_recent": "most recent",
"all": "all",
"videos": "videos",
"playlists": "playlists",
"more_filters": "more filters",
"more": "more",
"user_name": "User's Name (Exact)",
"category": "category",
"sub_category": "Sub-Category",
"reset": "reset",
"filters": "filters"
},
"lists": {
"no_results": "no results"
},
"bookmarks": {
"select_list": "select a list",
"all_bookmarks": "all bookmarked videos",
"bookmark_list": "bookmark list",
"bookmark_lists": "bookmark lists",
"no_bookmarks_lists": "no bookmark lists",
"new_list": "new list"
},
"video": {
"show_more": "show more",
"show_less": "show less",
"copy_link_video": "copy link video",
"subscribe_description": "Subscribing to a name lets you see their content on the Subscriptions tab of the Home Page. This does NOT download any data to your node.",
"follow_description": "Following a name automatically downloads all of its content to your node. The more followers a name has, the faster its content will download for everyone.",
"currentSize": "{{followerName}}'s current download size: {{followingSize}}",
"itemCount": "number of files: {{followingItemCount}}"
},
"payments": {
"amount": "amount"
},
"comments": {
"comment_here": "comment here",
"load_more_comments": "load more comments",
"submit_reply": "submit reply",
"hide_replies": "hide all replies",
"view_replies": "view all replies"
}
}

View File

@@ -0,0 +1,89 @@
{
"categories": {
"1": "Películas",
"2": "Series",
"3": "Música",
"4": "Educación",
"5": "Estilo de vida",
"6": "Videojuegos",
"7": "Tecnología",
"8": "Deportes",
"9": "Noticias y Política",
"10": "Cocina y Comida",
"11": "Animación",
"12": "Ciencia",
"13": "Salud y Bienestar",
"14": "Bricolaje y Manualidades",
"15": "Niños y Familia",
"16": "Comedia",
"17": "Viajes y Aventura",
"18": "Arte y Diseño",
"19": "Naturaleza y Medio Ambiente",
"20": "Negocios y Finanzas",
"21": "Desarrollo Personal",
"22": "Religión",
"23": "Historia",
"24": "Anime",
"25": "Caricaturas",
"26": "Qortal",
"27": "Paranormal",
"28": "Espiritualidad",
"29": "Privacidad",
"99": "Otro"
},
"subcategories": {
"101": "Acción y Aventura",
"102": "Comedia",
"103": "Drama",
"104": "Fantasía y Ciencia Ficción",
"105": "Terror y Suspenso",
"106": "Documentales",
"107": "Animadas",
"108": "Familiar e Infantil",
"109": "Romance",
"110": "Misterio y Crimen",
"111": "Históricas y de Guerra",
"112": "Musicales y Películas Musicales",
"113": "Cine Independiente",
"114": "Películas Internacionales",
"115": "Biografías e Historias Reales",
"199": "Otro",
"201": "Dramas",
"202": "Comedias",
"203": "Realidad y Competencia",
"204": "Documentales y Docuseries",
"205": "Ciencia Ficción y Fantasía",
"206": "Crimen y Misterio",
"207": "Series Animadas",
"208": "Niños y Familia",
"209": "Históricas y de Época",
"210": "Acción y Aventura",
"211": "Terror y Suspenso",
"212": "Romance",
"213": "Antologías",
"214": "Series Internacionales",
"215": "Miniseries",
"299": "Otro",
"400": "Tutorial",
"401": "Qortal",
"402": "Documental",
"499": "Otro",
"2401": "Kodomomuke",
"2402": "Shonen",
"2403": "Shoujo",
"2404": "Seinen",
"2405": "Josei",
"2406": "Mecha",
"2407": "Mahou Shoujo",
"2408": "Isekai",
"2409": "Yaoi",
"2410": "Yuri",
"2411": "Harem",
"2412": "Ecchi",
"2413": "Ídolos",
"2499": "Otro"
}
}

View File

@@ -0,0 +1,74 @@
{
"action": {
"back": "atrás",
"next": "siguiente",
"cancel": "cancelar"
},
"navbar": {
"search": "buscar"
},
"publish": {
"publish_action": "publicar",
"video": "video",
"playlist": "lista de reproducción",
"downloads": "descargas",
"update_video": "actualizar video",
"publish_videos": "publicar videos",
"populate_titles": "Rellenar títulos por nombre de archivo (cuando se seleccionan los archivos)",
"same_cover_images": "Todos los videos usan la misma imagen de portada",
"populate_description": "Rellenar todas las descripciones por título",
"drag_drop_videos": "Arrastra y suelta archivos de video aquí o haz clic para seleccionar archivos",
"supported_containers": "Contenedores de archivos compatibles",
"audio_codecs": "códecs de audio",
"video_codecs": "códecs de video",
"unsupported_codecs_description": "Usar códecs no compatibles puede hacer que el video o audio no funcionen correctamente",
"select_category": "Seleccionar una categoría",
"select_subcategory": "Seleccionar una subcategoría",
"add_cover_image": "agregar imagen de portada",
"description_video": "descripción del video",
"add_vids_playlist": "¿Quieres agregar estos videos a una lista de reproducción?",
"add_vids_playlist_optional": "agregar a una lista de reproducción es OPCIONAL",
"no_playlist": "sin lista de reproducción",
"new_playlist": "nueva lista de reproducción",
"existing_playlist": "lista de reproducción existente",
"select_existing_playlist": "seleccionar lista de reproducción existente",
"playlist_search_by_title": "buscar por título",
"title_playlist": "título de la lista de reproducción",
"description_playlist": "descripción de la lista de reproducción",
"generationg_extracts": "generando extractos de imagen",
"create_new_playlist": "crear nueva lista de reproducción",
"update_playlist_properties": "actualizar propiedades de la lista"
},
"notification": {
"no_new_notification": "no hay nuevas notificaciones",
"from_user": "de {{user}}"
},
"likes": {
"super_like": "súper me gusta",
"recent_super_likes": "súper me gustas recientes"
},
"blocked": {
"blocked_names": "nombres bloqueados"
},
"sidenav": {
"home": "inicio",
"subscriptions": "suscripciones",
"watched_videos": "videos vistos",
"bookmarks": "marcadores",
"your_playlists": "tus listas de reproducción",
"your_videos": "tus videos"
},
"filters": {
"most_recent": "más reciente",
"all": "todos",
"videos": "videos",
"playlists": "listas de reproducción",
"more_filters": "más filtros",
"more": "más",
"user_name": "Nombre del usuario (exacto)",
"category": "categoría",
"sub_category": "subcategoría",
"reset": "reiniciar",
"filters": "filtros"
}
}

View File

@@ -4,6 +4,27 @@ export const capitalizeAll = {
process: (value: string) => value.toUpperCase(),
};
export const capitalizeEachFirstChar = {
type: 'postProcessor',
name: 'capitalizeEachFirstChar',
process: (value: string) => {
if (!value?.trim()) return value;
const leadingSpaces = value.match(/^\s*/)?.[0] || '';
const trailingSpaces = value.match(/\s*$/)?.[0] || '';
const core = value
.trim()
.split(/\s+/)
.map(
(word) =>
word.charAt(0).toLocaleUpperCase() + word.slice(1).toLocaleLowerCase()
)
.join(' ');
return leadingSpaces + core + trailingSpaces;
},
};
export const capitalizeFirstChar = {
type: 'postProcessor',
name: 'capitalizeFirstChar',

View File

@@ -27,8 +27,11 @@ import { PageSubTitle } from '../../components/common/General/GeneralStyles.tsx'
import EditIcon from '@mui/icons-material/Edit';
import { PageTransition } from '../../components/common/PageTransition.tsx';
import { useIsSmall } from '../../hooks/useIsSmall.tsx';
import { useTranslation } from 'react-i18next';
export const Bookmarks = () => {
const { t } = useTranslation(['core']);
const isSmall = useIsSmall();
const [selectedList, setSelectedList, isHydratedSelectedList] =
usePersistedState('selectedBookmarkList', 0);
@@ -171,12 +174,18 @@ export const Bookmarks = () => {
width: '320px',
}}
>
<InputLabel id="bookmark-list-label">Select a List</InputLabel>
<InputLabel id="bookmark-list-label">
{t('core:bookmarks.select_list', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="bookmark-list-label"
value={selectedList?.id || 0}
label="Select a List"
label={t('core:bookmarks.select_list', {
postProcess: 'capitalizeFirstChar',
})}
onChange={handleChange}
displayEmpty
>
@@ -200,7 +209,11 @@ export const Bookmarks = () => {
alignItems: isSmall ? 'center' : 'flex-start',
}}
>
<PageSubTitle>all bookmarked videos</PageSubTitle>
<PageSubTitle>
{t('core:bookmarks.all_bookmarks', {
postProcess: 'capitalizeFirstChar',
})}
</PageSubTitle>
<Spacer height="14px" />
<Divider flexItem />
<Spacer height="20px" />
@@ -230,12 +243,18 @@ export const Bookmarks = () => {
return (
<PageTransition>
<FormControl fullWidth>
<InputLabel id="bookmark-list-label">Select a List</InputLabel>
<InputLabel id="bookmark-list-label">
{t('core:bookmarks.select_list', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="bookmark-list-label"
value={selectedList?.id || 0}
label="Select a List"
label={t('core:bookmarks.select_list', {
postProcess: 'capitalizeFirstChar',
})}
onChange={handleChange}
displayEmpty
>
@@ -261,7 +280,10 @@ export const Bookmarks = () => {
alignSelf: 'flex-start',
}}
>
Bookmark list: {selectedList?.title}
{t('core:bookmarks.bookmark_list', {
postProcess: 'capitalizeFirstChar',
})}
: {selectedList?.title}
</PageSubTitle>
<ButtonBase>
<EditIcon onClick={() => setIsOpenEdit(true)} />
@@ -321,7 +343,9 @@ export const Bookmarks = () => {
marginRight: 'auto',
}}
>
Delete
{t('core:action.delete', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
<Button
variant="contained"
@@ -330,7 +354,9 @@ export const Bookmarks = () => {
setNewTitle(selectedList?.title);
}}
>
Close
{t('core:action.close', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
<Button
disabled={!newTitle.trim() || selectedList?.title === newTitle}
@@ -346,7 +372,9 @@ export const Bookmarks = () => {
setIsOpenEdit(false);
}}
>
save
{t('core:action.save', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</DialogActions>
</Dialog>

View File

@@ -7,8 +7,11 @@ import { ChannelActions } from '../VideoContent/ChannelActions.tsx';
import { StyledCardHeaderComment } from '../VideoContent/VideoContent-styles.tsx';
import { HeaderContainer, ProfileContainer } from './Profile-styles.tsx';
import { PageTransition } from '../../../components/common/PageTransition.tsx';
import { useTranslation } from 'react-i18next';
export const IndividualProfile = () => {
const { t } = useTranslation(['core']);
const { name: channelName } = useParams();
const [selectedTab, setSelectedTab] = useState(0);
const { name, section } = useParams();
@@ -54,8 +57,16 @@ export const IndividualProfile = () => {
onChange={handleTabChange}
aria-label="profile tabs"
>
<Tab label="Videos" />
<Tab label="Playlists" />
<Tab
label={t('core:filters.videos', {
postProcess: 'capitalizeFirstChar',
})}
/>
<Tab
label={t('core:filters.playlists', {
postProcess: 'capitalizeFirstChar',
})}
/>
</Tabs>
</Box>

View File

@@ -29,6 +29,7 @@ import { PageTransition } from '../../../components/common/PageTransition.tsx';
import { useIsSmall } from '../../../hooks/useIsSmall.tsx';
import { VideoContentContainer } from '../VideoContent/VideoContent-styles.tsx';
import { CollapsibleDescription } from '../VideoContent/VideoContent.tsx';
import { useTranslation } from 'react-i18next';
export const PlaylistContent = () => {
const {
@@ -52,6 +53,8 @@ export const PlaylistContent = () => {
descriptionThreshold,
loadingSuperLikes,
} = usePlaylistContentState();
const { t } = useTranslation(['core']);
const navigate = useNavigate();
const isSmall = useIsSmall();
const isScreenSmall = !useMediaQuery(`(min-width:950px)`);
@@ -80,7 +83,11 @@ export const PlaylistContent = () => {
display: 'flex',
}}
>
<Typography>This playlist doesn't exist</Typography>
<Typography>
{t('core:publish.playlist_not_exist', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box>
) : (
<Box

View File

@@ -12,6 +12,7 @@ import {
FileAttachmentFont,
} from './VideoContent-styles.tsx';
import { AddToBookmarks } from '../../../components/common/ContentButtons/AddToBookmarks.tsx';
import { useTranslation } from 'react-i18next';
export interface VideoActionsBarProps {
channelName: string;
@@ -34,6 +35,8 @@ export const VideoActionsBar = ({
setSuperLikeList,
sx,
}: VideoActionsBarProps) => {
const { t } = useTranslation(['core']);
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superLikeList?.reduce((acc, curr) => {
if (curr?.amount && !isNaN(parseFloat(curr.amount)))
@@ -123,7 +126,9 @@ export const VideoActionsBar = ({
<IndexButton channelName={channelName} />
<CopyLinkButton
link={`qortal://APP/Q-Tube/video/${encodeURIComponent(videoData?.user)}/${encodeURIComponent(videoData?.id)}`}
tooltipTitle={`Copy video link`}
tooltipTitle={t('core:video.copy_link_video', {
postProcess: 'capitalizeFirstChar',
})}
/>
</Box>
{videoData && (

View File

@@ -28,6 +28,7 @@ import { handleClickText, processText } from 'qapp-core';
import { PageTransition } from '../../../components/common/PageTransition.tsx';
import { useIsMobile } from '../../../hooks/useIsMobile.tsx';
import { useIsSmall } from '../../../hooks/useIsSmall.tsx';
import { useTranslation } from 'react-i18next';
function flattenHtml(html: string): string {
const sanitize: string = DOMPurify.sanitize(html, {
@@ -44,6 +45,8 @@ export const CollapsibleDescription = ({
text?: string;
html?: any;
}) => {
const { t } = useTranslation(['core']);
const [expanded, setExpanded] = useState(false);
const [showMore, setShowMore] = useState(false);
const textRef = useRef<HTMLDivElement>(null);
@@ -109,7 +112,13 @@ export const CollapsibleDescription = ({
size="small"
sx={{ mt: 1, textTransform: 'none' }}
>
{expanded ? 'Show less' : 'Show more'}
{expanded
? t('core:video.show_less', {
postProcess: 'capitalizeFirstChar',
})
: t('core:video.show_more', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
)}
</Box>
@@ -209,8 +218,10 @@ export const VideoContent = () => {
color="textPrimary"
sx={{
textAlign: 'start',
marginTop: isSmall ? '20px' : '10px',
fontSize: isSmall ? '18px' : 'unset',
marginTop: '20px',
...(isSmall && {
fontSize: '18px',
}),
}}
>
{videoData?.title}

View File

@@ -16,12 +16,15 @@ import { editVideoAtom } from '../../../state/publish/video.ts';
import { scrollRefAtom } from '../../../state/global/navbar.ts';
import { Box, Typography } from '@mui/material';
import { useIsMobile } from '../../../hooks/useIsMobile.tsx';
import { useTranslation } from 'react-i18next';
interface VideoListProps {
searchParameters: QortalSearchParams;
listName: string;
}
export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
const { t } = useTranslation(['core']);
const { name: username } = useAuth();
const setEditVideo = useSetAtom(editVideoAtom);
const { addToBlockedList } = useBlockedNames();
@@ -42,23 +45,31 @@ export const VideoList = ({ searchParameters, listName }: VideoListProps) => {
return <VideoLoaderItem status={status} />;
}, []);
const renderLoaderList = useCallback((status: LoaderListStatus) => {
if (status === 'NO_RESULTS') {
return (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
marginTop: '20px',
}}
>
<Typography>No results</Typography>
</Box>
);
}
return <VideoLoaderItem status={status} />;
}, []);
const renderLoaderList = useCallback(
(status: LoaderListStatus) => {
if (status === 'NO_RESULTS') {
return (
<Box
sx={{
width: '100%',
display: 'flex',
justifyContent: 'center',
marginTop: '20px',
}}
>
<Typography>
{' '}
{t('core:lists.no_results', {
postProcess: 'capitalizeFirstChar',
})}
</Typography>
</Box>
);
}
return <VideoLoaderItem status={status} />;
},
[t]
);
const renderListItem = useCallback(
(item, index) => {

View File

@@ -26,6 +26,7 @@ import { useState } from 'react';
import { editPlaylistAtom } from '../../../state/publish/playlist';
import { useSetAtom } from 'jotai';
import { useIsMobile } from '../../../hooks/useIsMobile';
import { useTranslation } from 'react-i18next';
export const VideoListItem = ({
qortalMetadata,
@@ -37,6 +38,8 @@ export const VideoListItem = ({
disableActions,
handleRemoveVideoFromList,
}: any) => {
const { t } = useTranslation(['core']);
const isMobile = useIsMobile();
const navigate = useNavigate();
@@ -70,7 +73,12 @@ export const VideoListItem = ({
}}
>
{qortalMetadata?.name === username && (
<Tooltip title="Edit playlist" placement="top">
<Tooltip
title={t('core:publish.edit_playlist', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<BlockIconContainer>
<EditIcon
onClick={() => {
@@ -95,7 +103,12 @@ export const VideoListItem = ({
)}
{qortalMetadata?.name !== username && (
<Tooltip title="Block user content" placement="top">
<Tooltip
title={t('core:publish.block_user_content', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<BlockIconContainer>
<BlockIcon
onClick={() => {
@@ -220,7 +233,12 @@ export const VideoListItem = ({
{qortalMetadata?.name === username &&
!isBookmarks &&
!disableActions && (
<Tooltip title="Edit video properties" placement="top">
<Tooltip
title={t('core:publish.edit_video', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<BlockIconContainer>
<EditIcon
onClick={() => {
@@ -247,7 +265,12 @@ export const VideoListItem = ({
{qortalMetadata?.name !== username &&
!isBookmarks &&
!disableActions && (
<Tooltip title="Block user content" placement="top">
<Tooltip
title={t('core:publish.block_user_content', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<BlockIconContainer>
<BlockIcon
onClick={() => {
@@ -260,7 +283,12 @@ export const VideoListItem = ({
{qortalMetadata?.name === username &&
!isBookmarks &&
!disableActions && (
<Tooltip title="Delete video" placement="top">
<Tooltip
title={t('core:publish.delete_video', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<BlockIconContainer>
<DeleteIcon
onClick={() => {
@@ -271,7 +299,12 @@ export const VideoListItem = ({
</Tooltip>
)}
{isBookmarks && handleRemoveVideoFromList && !disableActions && (
<Tooltip title="Remove video from list" placement="top">
<Tooltip
title={t('core:publish.remove_video_list', {
postProcess: 'capitalizeFirstChar',
})}
placement="top"
>
<BlockIconContainer>
<DeleteIcon
onClick={() => {

View File

@@ -35,6 +35,7 @@ import { useSidebarState } from './Components/SearchSidebar-State';
import { categories, subCategories } from '../../constants/Categories';
import { useIsSmall } from '../../hooks/useIsSmall';
import { ListSuperLikeContainer } from '../../components/common/ListSuperLikes/ListSuperLikeContainer';
import { useTranslation } from 'react-i18next';
export const CustomChip = styled(Chip)(({ theme }) => ({
backgroundColor: theme.palette.background.unSelected, // dark background
@@ -58,6 +59,8 @@ export const CustomChip = styled(Chip)(({ theme }) => ({
}));
export const FilterOptions = () => {
const { t } = useTranslation(['core', 'category']);
const isSmall = useIsSmall();
const tabsRef = useRef(null);
@@ -102,7 +105,9 @@ export const FilterOptions = () => {
const hasCategory = !!filterCategory;
return [
{
label: 'Most Recent',
label: t('core:filters.most_recent', {
postProcess: 'capitalizeFirstChar',
}),
filterMode: 'recent',
isSelected: !hasCategory && filterMode === 'recent',
color:
@@ -111,7 +116,9 @@ export const FilterOptions = () => {
: 'text.primary',
},
{
label: 'All',
label: t('core:filters.all', {
postProcess: 'capitalizeFirstChar',
}),
filterMode: 'all',
isSelected: !hasCategory && filterMode === 'all',
color:
@@ -120,7 +127,7 @@ export const FilterOptions = () => {
: 'text.primary',
},
{
label: 'Politics',
label: t(`category:categories.${9}`),
filterMode: 'all',
filterCategory: 9,
isSelected: filterCategory?.id === 9,
@@ -128,7 +135,7 @@ export const FilterOptions = () => {
filterCategory?.id === 9 ? 'primary.contrastText' : 'text.primary',
},
{
label: 'TV Shows',
label: t(`category:categories.${2}`),
filterMode: 'all',
filterCategory: 2,
isSelected: filterCategory?.id === 2,
@@ -136,7 +143,7 @@ export const FilterOptions = () => {
filterCategory?.id === 2 ? 'primary.contrastText' : 'text.primary',
},
{
label: 'Movies',
label: t(`category:categories.${1}`),
filterMode: 'all',
filterCategory: 1,
isSelected: filterCategory?.id === 1,
@@ -144,7 +151,7 @@ export const FilterOptions = () => {
filterCategory?.id === 1 ? 'primary.contrastText' : 'text.primary',
},
];
}, [filterMode, filterCategory]);
}, [filterMode, filterCategory, t]);
const handleClose = () => {
setIsOpen(false);
@@ -173,7 +180,9 @@ export const FilterOptions = () => {
label={
<CustomChip
icon={<AddIcon fontSize="small" />}
label="More"
label={t('core:filters.more', {
postProcess: 'capitalizeFirstChar',
})}
clickable
onClick={() => setIsOpen(true)}
sx={(theme) => {
@@ -211,7 +220,9 @@ export const FilterOptions = () => {
label={
<CustomChip
icon={<PlayCircleOutlineIcon fontSize="small" />}
label="Videos"
label={t('core:filters.videos', {
postProcess: 'capitalizeFirstChar',
})}
clickable
onClick={() => setFilterType('videos')}
sx={(theme) => {
@@ -249,7 +260,9 @@ export const FilterOptions = () => {
label={
<CustomChip
icon={<PlaylistPlayIcon fontSize="small" />}
label="Playlists"
label={t('core:filters.playlists', {
postProcess: 'capitalizeFirstChar',
})}
clickable
onClick={() => setFilterType('playlists')}
sx={(theme) => {
@@ -348,7 +361,9 @@ export const FilterOptions = () => {
>
<CustomChip
icon={<PlayCircleOutlineIcon fontSize="small" />}
label="Videos"
label={t('core:filters.videos', {
postProcess: 'capitalizeFirstChar',
})}
clickable
onClick={() => setFilterType('videos')}
sx={(theme) => {
@@ -372,7 +387,9 @@ export const FilterOptions = () => {
/>
<CustomChip
icon={<PlaylistPlayIcon fontSize="small" />}
label="Playlists"
label={t('core:filters.playlists', {
postProcess: 'capitalizeFirstChar',
})}
clickable
onClick={() => setFilterType('playlists')}
sx={(theme) => {
@@ -435,7 +452,9 @@ export const FilterOptions = () => {
})}
<CustomChip
icon={<AddIcon fontSize="small" />}
label="More Filters"
label={t('core:filters.more_filters', {
postProcess: 'capitalizeFirstChar',
})}
clickable
onClick={() => setIsOpen(true)}
sx={(theme) => {
@@ -462,7 +481,11 @@ export const FilterOptions = () => {
)}
<Dialog open={isOpen} fullWidth={true} maxWidth={'sm'}>
<DialogTitle>Filters</DialogTitle>
<DialogTitle>
{t('core:filters.filters', {
postProcess: 'capitalizeFirstChar',
})}
</DialogTitle>
<IconButton
aria-label="close"
onClick={handleClose}
@@ -512,7 +535,9 @@ export const FilterOptions = () => {
setFilterNameState(e.target.value);
}}
value={filterNameState}
placeholder="User's Name (Exact)"
placeholder={t('core:filters.user_name', {
postProcess: 'capitalizeFirstChar',
})}
sx={{
marginTop: '20px',
borderBottom: '1px solid',
@@ -551,11 +576,19 @@ export const FilterOptions = () => {
}}
id="Category"
>
Category
{t('core:filters.category', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="Category"
input={<OutlinedInput label="Category" />}
labelId={'category'}
input={
<OutlinedInput
label={t('core:filters.category', {
postProcess: 'capitalizeFirstChar',
})}
/>
}
value={selectedCategoryVideos?.id || ''}
onChange={handleOptionCategoryChangeVideos}
sx={{
@@ -578,7 +611,7 @@ export const FilterOptions = () => {
>
{categories.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:categories.${option.id}`)}
</MenuItem>
))}
</Select>
@@ -592,11 +625,19 @@ export const FilterOptions = () => {
}}
id="Sub-Category"
>
Sub-Category
{t('core:filters.sub_category', {
postProcess: 'capitalizeFirstChar',
})}
</InputLabel>
<Select
labelId="Sub-Category"
input={<OutlinedInput label="Sub-Category" />}
input={
<OutlinedInput
label={t('core:filters.sub_category', {
postProcess: 'capitalizeFirstChar',
})}
/>
}
value={selectedSubCategoryVideos?.id || ''}
onChange={(e) =>
handleOptionSubCategoryChangeVideos(
@@ -625,7 +666,7 @@ export const FilterOptions = () => {
{subCategories[selectedCategoryVideos.id].map(
(option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
{t(`category:subcategories.${option.id}`)}
</MenuItem>
)
)}
@@ -647,7 +688,9 @@ export const FilterOptions = () => {
}}
variant="contained"
>
reset
{t('core:filters.reset', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
<Button
onClick={() => {
@@ -660,7 +703,9 @@ export const FilterOptions = () => {
}}
variant="contained"
>
Search
{t('core:navbar.search', {
postProcess: 'capitalizeFirstChar',
})}
</Button>
</FiltersContainer>
</Box>

View File

@@ -14,8 +14,11 @@ import { useHomeState } from '../Home/Home-State.ts';
import VideoList from '../Home/Components/VideoList.tsx';
import { PageSubTitle } from '../../components/common/General/GeneralStyles.tsx';
import { PageTransition } from '../../components/common/PageTransition.tsx';
import { useTranslation } from 'react-i18next';
export const Subscriptions = () => {
const { t } = useTranslation(['core']);
const [searchParams, setSearchParams] = useSearchParams();
const scrollRef = useAtomValue(scrollRefAtom);
@@ -71,7 +74,9 @@ export const Subscriptions = () => {
alignSelf: 'flex-start',
}}
>
subscriptions
{t('core:sidenav.subscriptions', {
postProcess: 'capitalizeFirstChar',
})}
</PageSubTitle>
<Spacer height="14px" />
<Divider flexItem />