mirror of
https://github.com/Qortal/q-tube.git
synced 2026-04-22 08:03:59 +00:00
started translations
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
89
src/i18n/locales/en/category.json
Normal file
89
src/i18n/locales/en/category.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
89
src/i18n/locales/es/category.json
Normal file
89
src/i18n/locales/es/category.json
Normal 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"
|
||||
}
|
||||
}
|
||||
74
src/i18n/locales/es/core.json
Normal file
74
src/i18n/locales/es/core.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user