feat: add internationalization support

This commit is contained in:
AshAnand34
2025-07-12 23:02:35 -07:00
parent 3b702b260c
commit f22bb8bd57
149 changed files with 2807 additions and 1045 deletions

View File

@@ -9,6 +9,7 @@ import ToolFileResult from '@components/result/ToolFileResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
import { useTranslation } from 'react-i18next';
const initialValues: InitialValuesType = {
newSpeed: 2
@@ -18,6 +19,7 @@ export default function ChangeSpeed({
title,
longDescription
}: ToolComponentProps) {
const { t } = useTranslation();
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -128,13 +130,13 @@ export default function ChangeSpeed({
updateField
}) => [
{
title: 'New Video Speed',
title: t('video.changeSpeed.newVideoSpeed'),
component: (
<Box>
<TextFieldWithDesc
value={values.newSpeed.toString()}
onOwnChange={(val) => updateField('newSpeed', Number(val))}
description="Default multiplier: 2 means 2x faster"
description={t('video.changeSpeed.defaultMultiplier')}
type="number"
/>
</Box>
@@ -149,21 +151,32 @@ export default function ChangeSpeed({
<ToolVideoInput
value={input}
onChange={setInput}
title={'Input Video'}
title={t('video.changeSpeed.inputTitle')}
/>
}
resultComponent={
loading ? (
<ToolFileResult title="Setting Speed" value={null} loading={true} />
<ToolFileResult
title={t('video.changeSpeed.settingSpeed')}
value={null}
loading={true}
/>
) : (
<ToolFileResult title="Edited Video" value={result} extension="mp4" />
<ToolFileResult
title={t('video.changeSpeed.resultTitle')}
value={result}
extension="mp4"
/>
)
}
initialValues={initialValues}
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
toolInfo={{
title: t('video.changeSpeed.toolInfo.title', { title }),
description: longDescription
}}
/>
);
}

View File

@@ -2,12 +2,17 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Change speed',
name: 'Change Video Speed',
path: 'change-speed',
icon: 'material-symbols-light:speed-outline',
icon: 'material-symbols:speed',
description:
'This online utility lets you change the speed of a video. You can speed it up or slow it down.',
shortDescription: 'Quickly change video speed',
keywords: ['change', 'speed'],
component: lazy(() => import('./index'))
'Change the playback speed of video files. Speed up or slow down videos while maintaining audio synchronization. Supports various speed multipliers and common video formats.',
shortDescription: 'Change video playback speed',
keywords: ['video', 'speed', 'playback', 'fast', 'slow'],
component: lazy(() => import('./index')),
i18n: {
name: 'video.changeSpeed.name',
description: 'video.changeSpeed.description',
shortDescription: 'video.changeSpeed.shortDescription'
}
});

View File

@@ -11,6 +11,7 @@ import { compressVideo, VideoResolution } from './service';
import SimpleRadio from '@components/options/SimpleRadio';
import Slider from 'rc-slider';
import 'rc-slider/assets/index.css';
import { useTranslation } from 'react-i18next';
export const initialValues = {
width: 480 as VideoResolution,
@@ -68,6 +69,7 @@ const presetOptions = [
];
export default function CompressVideo({ title }: ToolComponentProps) {
const { t } = useTranslation();
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -100,7 +102,7 @@ export default function CompressVideo({ title }: ToolComponentProps) {
updateField
}) => [
{
title: 'Resolution',
title: t('video.compress.resolution'),
component: (
<Box>
{resolutionOptions.map((option) => (
@@ -117,7 +119,7 @@ export default function CompressVideo({ title }: ToolComponentProps) {
)
},
{
title: 'Quality (CRF)',
title: t('video.compress.quality'),
component: (
<Box sx={{ mb: 2 }}>
<Slider
@@ -129,9 +131,9 @@ export default function CompressVideo({ title }: ToolComponentProps) {
updateField('crf', typeof value === 'number' ? value : value[0]);
}}
marks={{
0: 'Lossless',
23: 'Default',
51: 'Worst'
0: t('video.compress.lossless'),
23: t('video.compress.default'),
51: t('video.compress.worst')
}}
/>
</Box>
@@ -160,16 +162,16 @@ export default function CompressVideo({ title }: ToolComponentProps) {
<ToolVideoInput
value={input}
onChange={setInput}
title={'Input Video'}
title={t('video.compress.inputTitle')}
/>
}
resultComponent={
<ToolFileResult
title={'Compressed Video'}
title={t('video.compress.resultTitle')}
value={result}
extension={'mp4'}
loading={loading}
loadingText={'Compressing video...'}
loadingText={t('video.compress.loadingText')}
/>
}
initialValues={initialValues}

View File

@@ -16,5 +16,10 @@ export const tool = defineTool('video', {
'resolution',
'reduce size'
],
component: lazy(() => import('./index'))
component: lazy(() => import('./index')),
i18n: {
name: 'video.compress.name',
description: 'video.compress.description',
shortDescription: 'video.compress.shortDescription'
}
});

View File

@@ -1,13 +1,14 @@
import { Box, TextField, Typography, Alert } from '@mui/material';
import { useCallback, useState, useEffect } from 'react';
import ToolFileResult from '@components/result/ToolFileResult';
import { Box, Typography, TextField, Alert } from '@mui/material';
import React, { useState, useCallback } from 'react';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
import { debounce } from 'lodash';
import ToolVideoInput from '@components/input/ToolVideoInput';
import { cropVideo, getVideoDimensions } from './service';
import { InitialValuesType } from './types';
import ToolVideoInput from '@components/input/ToolVideoInput';
import { GetGroupsType } from '@components/options/ToolOptions';
import ToolFileResult from '@components/result/ToolFileResult';
import { debounce } from 'lodash';
import { useTranslation } from 'react-i18next';
const initialValues: InitialValuesType = {
x: 0,
@@ -17,6 +18,7 @@ const initialValues: InitialValuesType = {
};
export default function CropVideo({ title }: ToolComponentProps) {
const { t } = useTranslation();
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -30,19 +32,23 @@ export default function CropVideo({ title }: ToolComponentProps) {
if (!videoDimensions) return '';
if (values.x < 0 || values.y < 0) {
return 'X and Y coordinates must be non-negative';
return t('video.cropVideo.errorNonNegativeCoordinates');
}
if (values.width <= 0 || values.height <= 0) {
return 'Width and height must be positive';
return t('video.cropVideo.errorPositiveDimensions');
}
if (values.x + values.width > videoDimensions.width) {
return `Crop area extends beyond video width (${videoDimensions.width}px)`;
return t('video.cropVideo.errorBeyondWidth', {
width: videoDimensions.width
});
}
if (values.y + values.height > videoDimensions.height) {
return `Crop area extends beyond video height (${videoDimensions.height}px)`;
return t('video.cropVideo.errorBeyondHeight', {
height: videoDimensions.height
});
}
return '';
@@ -68,9 +74,7 @@ export default function CropVideo({ title }: ToolComponentProps) {
setResult(croppedFile);
} catch (error) {
console.error('Error cropping video:', error);
setProcessingError(
'Error cropping video. Please check parameters and video file.'
);
setProcessingError(t('video.cropVideo.errorCroppingVideo'));
} finally {
setLoading(false);
}
@@ -86,24 +90,26 @@ export default function CropVideo({ title }: ToolComponentProps) {
updateField
}) => [
{
title: 'Video Information',
title: t('video.cropVideo.videoInformation'),
component: (
<Box>
{videoDimensions ? (
<Typography variant="body2" sx={{ mb: 2 }}>
Video dimensions: {videoDimensions.width} ×{' '}
{videoDimensions.height} pixels
{t('video.cropVideo.videoDimensions', {
width: videoDimensions.width,
height: videoDimensions.height
})}
</Typography>
) : (
<Typography variant="body2" sx={{ mb: 2 }}>
Load a video to see dimensions
{t('video.cropVideo.loadVideoForDimensions')}
</Typography>
)}
</Box>
)
},
{
title: 'Crop Coordinates',
title: t('video.cropVideo.cropCoordinates'),
component: (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{processingError && (
@@ -113,7 +119,7 @@ export default function CropVideo({ title }: ToolComponentProps) {
)}
<Box sx={{ display: 'flex', gap: 2 }}>
<TextField
label="X (left)"
label={t('video.cropVideo.xCoordinate')}
type="number"
value={values.x}
onChange={(e) => updateField('x', parseInt(e.target.value) || 0)}
@@ -121,7 +127,7 @@ export default function CropVideo({ title }: ToolComponentProps) {
inputProps={{ min: 0 }}
/>
<TextField
label="Y (top)"
label={t('video.cropVideo.yCoordinate')}
type="number"
value={values.y}
onChange={(e) => updateField('y', parseInt(e.target.value) || 0)}
@@ -131,7 +137,7 @@ export default function CropVideo({ title }: ToolComponentProps) {
</Box>
<Box sx={{ display: 'flex', gap: 2 }}>
<TextField
label="Width"
label={t('video.cropVideo.width')}
type="number"
value={values.width}
onChange={(e) =>
@@ -141,7 +147,7 @@ export default function CropVideo({ title }: ToolComponentProps) {
inputProps={{ min: 1 }}
/>
<TextField
label="Height"
label={t('video.cropVideo.height')}
type="number"
value={values.height}
onChange={(e) =>
@@ -183,7 +189,9 @@ export default function CropVideo({ title }: ToolComponentProps) {
})
.catch((error) => {
console.error('Error getting video dimensions:', error);
setProcessingError('Failed to load video dimensions');
setProcessingError(
t('video.cropVideo.errorLoadingDimensions')
);
});
} else {
setVideoDimensions(null);
@@ -191,20 +199,20 @@ export default function CropVideo({ title }: ToolComponentProps) {
}
setInput(video);
}}
title={'Input Video'}
title={t('video.cropVideo.inputTitle')}
/>
)}
resultComponent={
loading ? (
<ToolFileResult
title={'Cropping Video'}
title={t('video.cropVideo.croppingVideo')}
value={null}
loading={true}
extension={''}
/>
) : (
<ToolFileResult
title={'Cropped Video'}
title={t('video.cropVideo.resultTitle')}
value={result}
extension={'mp4'}
/>

View File

@@ -2,13 +2,17 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Crop video',
name: 'Crop Video',
path: 'crop-video',
icon: 'mdi:crop',
description: 'Crop a video by specifying coordinates and dimensions',
shortDescription: 'Crop video to specific area',
keywords: ['crop', 'video', 'trim', 'cut', 'resize'],
longDescription:
'Remove unwanted parts from the edges of your video by cropping it to a specific rectangular area. Define the starting coordinates (X, Y) and the width and height of the area you want to keep.',
component: lazy(() => import('./index'))
icon: 'material-symbols:crop',
description:
'Crop video files to remove unwanted areas or focus on specific content. Specify crop dimensions and position to create custom video compositions.',
shortDescription: 'Crop video to remove unwanted areas',
keywords: ['video', 'crop', 'trim', 'edit', 'resize'],
component: lazy(() => import('./index')),
i18n: {
name: 'video.cropVideo.name',
description: 'video.cropVideo.description',
shortDescription: 'video.cropVideo.shortDescription'
}
});

View File

@@ -10,6 +10,7 @@ import ToolVideoInput from '@components/input/ToolVideoInput';
import { flipVideo } from './service';
import { FlipOrientation, InitialValuesType } from './types';
import SimpleRadio from '@components/options/SimpleRadio';
import { useTranslation } from 'react-i18next';
export const initialValues: InitialValuesType = {
orientation: 'horizontal'
@@ -30,6 +31,7 @@ const orientationOptions: { value: FlipOrientation; label: string }[] = [
];
export default function FlipVideo({ title }: ToolComponentProps) {
const { t } = useTranslation();
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -58,13 +60,13 @@ export default function FlipVideo({ title }: ToolComponentProps) {
updateField
}) => [
{
title: 'Orientation',
title: t('video.flip.orientation'),
component: (
<Box>
{orientationOptions.map((orientationOption) => (
<SimpleRadio
key={orientationOption.value}
title={orientationOption.label}
title={t(`video.flip.${orientationOption.value}Label`)}
checked={values.orientation === orientationOption.value}
onClick={() => {
updateField('orientation', orientationOption.value);
@@ -84,20 +86,20 @@ export default function FlipVideo({ title }: ToolComponentProps) {
<ToolVideoInput
value={input}
onChange={setInput}
title={'Input Video'}
title={t('video.flip.inputTitle')}
/>
}
resultComponent={
loading ? (
<ToolFileResult
title={'Flipping Video'}
title={t('video.flip.flippingVideo')}
value={null}
loading={true}
extension={''}
/>
) : (
<ToolFileResult
title={'Flipped Video'}
title={t('video.flip.resultTitle')}
value={result}
extension={'mp4'}
/>

View File

@@ -4,12 +4,15 @@ import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Flip Video',
path: 'flip',
icon: 'mdi:flip-horizontal',
icon: 'material-symbols:flip',
description:
'This online utility allows you to flip videos horizontally or vertically. You can preview the flipped video before processing. Supports common video formats like MP4, WebM, and OGG.',
shortDescription: 'Flip videos horizontally or vertically',
keywords: ['flip', 'video', 'mirror', 'edit', 'horizontal', 'vertical'],
longDescription:
'Easily flip your videos horizontally (mirror) or vertically (upside down) with this simple online tool.',
component: lazy(() => import('./index'))
'Flip video files horizontally or vertically. Mirror videos for special effects or correct orientation issues.',
shortDescription: 'Flip video horizontally or vertically',
keywords: ['video', 'flip', 'mirror', 'horizontal', 'vertical'],
component: lazy(() => import('./index')),
i18n: {
name: 'video.flip.name',
description: 'video.flip.description',
shortDescription: 'video.flip.shortDescription'
}
});

View File

@@ -3,12 +3,17 @@ import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('gif', {
name: 'Change speed',
name: 'Change GIF Speed',
path: 'change-speed',
icon: 'material-symbols-light:speed-outline',
icon: 'material-symbols:speed',
description:
'This online utility lets you change the speed of a GIF animation. You can speed it up or slow it down. You can set the same constant delay between all frames or change the delays of individual frames. You can also play both the input and output GIFs at the same time and compare their speeds',
shortDescription: 'Quickly change GIF speed',
keywords: ['change', 'speed'],
component: lazy(() => import('./index'))
'Change the playback speed of GIF animations. Speed up or slow down GIFs while maintaining smooth animation.',
shortDescription: 'Change GIF animation speed',
keywords: ['gif', 'speed', 'animation', 'fast', 'slow'],
component: lazy(() => import('./index')),
i18n: {
name: 'gif.changeSpeed.name',
description: 'gif.changeSpeed.description',
shortDescription: 'gif.changeSpeed.shortDescription'
}
});

View File

@@ -0,0 +1,79 @@
{
"compress": {
"title": "Compress Video",
"description": "Reduce video file size while maintaining quality.",
"inputTitle": "Input Video",
"resultTitle": "Compressed Video",
"compressionOptions": "Compression Options",
"qualityDescription": "Video quality (1-100)",
"qualityPlaceholder": "Quality",
"toolInfo": {
"title": "Video Compression",
"description": "This tool allows you to compress video files to reduce their size while maintaining acceptable quality. You can adjust the compression level to balance between file size and video quality."
}
},
"rotate": {
"title": "Rotate Video",
"description": "Rotate video by specified degrees.",
"inputTitle": "Input Video",
"resultTitle": "Rotated Video",
"rotationOptions": "Rotation Options",
"rotationAngleDescription": "Rotation angle in degrees",
"anglePlaceholder": "Angle",
"toolInfo": {
"title": "Video Rotation",
"description": "This tool allows you to rotate video files by a specified angle. You can rotate videos by 90, 180, or 270 degrees, or any custom angle."
}
},
"flip": {
"title": "Flip Video",
"description": "Flip video horizontally or vertically.",
"inputTitle": "Input Video",
"resultTitle": "Flipped Video",
"flipOptions": "Flip Options",
"horizontalFlip": "Horizontal Flip",
"verticalFlip": "Vertical Flip",
"toolInfo": {
"title": "Video Flip",
"description": "This tool allows you to flip video files horizontally or vertically. Horizontal flip creates a mirror effect, while vertical flip turns the video upside down."
}
},
"loop": {
"title": "Loop Video",
"description": "Create a looping video by repeating the original video multiple times.",
"inputTitle": "Input Video",
"resultTitle": "Looped Video",
"loops": "Loops",
"numberOfLoops": "Number of Loops",
"loopingVideo": "Looping Video",
"toolInfo": {
"title": "What is a {{title}}?",
"description": "This tool allows you to create a looping video by repeating the original video multiple times. You can specify how many times the video should loop."
}
},
"cropVideo": {
"title": "Crop Video",
"description": "Crop video to remove unwanted areas.",
"inputTitle": "Input Video",
"resultTitle": "Cropped Video",
"croppingVideo": "Cropping Video",
"videoInformation": "Video Information",
"videoDimensions": "Video dimensions: {{width}} × {{height}} pixels",
"loadVideoForDimensions": "Load a video to see dimensions",
"cropCoordinates": "Crop Coordinates",
"xCoordinate": "X (left)",
"yCoordinate": "Y (top)",
"width": "Width",
"height": "Height",
"errorNonNegativeCoordinates": "X and Y coordinates must be non-negative",
"errorPositiveDimensions": "Width and height must be positive",
"errorBeyondWidth": "Crop area extends beyond video width ({{width}}px)",
"errorBeyondHeight": "Crop area extends beyond video height ({{height}}px)",
"errorCroppingVideo": "Error cropping video. Please check parameters and video file.",
"errorLoadingDimensions": "Failed to load video dimensions",
"toolInfo": {
"title": "Crop Video",
"description": "This tool allows you to crop video files to remove unwanted areas. You can specify the crop area by setting the X, Y coordinates and width, height dimensions."
}
}
}

View File

@@ -11,6 +11,7 @@ import ToolFileResult from '@components/result/ToolFileResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { updateNumberField } from '@utils/string';
import * as Yup from 'yup';
import { useTranslation } from 'react-i18next';
const initialValues: InitialValuesType = {
loops: 2
@@ -21,6 +22,7 @@ const validationSchema = Yup.object({
});
export default function Loop({ title, longDescription }: ToolComponentProps) {
const { t } = useTranslation();
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -43,7 +45,7 @@ export default function Loop({ title, longDescription }: ToolComponentProps) {
updateField
}) => [
{
title: 'Loops',
title: t('video.loop.loops'),
component: (
<Box>
<TextFieldWithDesc
@@ -51,7 +53,7 @@ export default function Loop({ title, longDescription }: ToolComponentProps) {
updateNumberField(value, 'loops', updateField)
}
value={values.loops}
label={'Number of Loops'}
label={t('video.loop.numberOfLoops')}
/>
</Box>
)
@@ -66,14 +68,14 @@ export default function Loop({ title, longDescription }: ToolComponentProps) {
loading ? (
<ToolFileResult
value={null}
title={'Looping Video'}
title={t('video.loop.loopingVideo')}
loading={true}
extension={''}
/>
) : (
<ToolFileResult
value={result}
title={'Looped Video'}
title={t('video.loop.resultTitle')}
extension={'mp4'}
/>
)
@@ -83,7 +85,10 @@ export default function Loop({ title, longDescription }: ToolComponentProps) {
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
toolInfo={{
title: t('video.loop.toolInfo.title', { title }),
description: longDescription
}}
/>
);
}

View File

@@ -4,10 +4,15 @@ import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Loop Video',
path: 'loop',
icon: 'ic:baseline-loop',
icon: 'material-symbols:loop',
description:
'This online utility lets you loop videos by specifying the number of repetitions. You can preview the looped video before processing. Supports common video formats like MP4, WebM, and OGG.',
shortDescription: 'Loop videos multiple times',
keywords: ['loop', 'video', 'repeat', 'duplicate', 'sequence', 'playback'],
component: lazy(() => import('./index'))
'Create looping video files that repeat continuously. Perfect for background videos, presentations, or creating seamless loops.',
shortDescription: 'Create looping video files',
keywords: ['video', 'loop', 'repeat', 'continuous'],
component: lazy(() => import('./index')),
i18n: {
name: 'video.loop.name',
description: 'video.loop.description',
shortDescription: 'video.loop.shortDescription'
}
});

View File

@@ -10,6 +10,7 @@ import ToolVideoInput from '@components/input/ToolVideoInput';
import { rotateVideo } from './service';
import { RotationAngle } from '../../pdf/rotate-pdf/types';
import SimpleRadio from '@components/options/SimpleRadio';
import { useTranslation } from 'react-i18next';
export const initialValues = {
rotation: 90
@@ -27,6 +28,7 @@ const angleOptions: { value: RotationAngle; label: string }[] = [
{ value: 270, label: '270° (90° Counter-clockwise)' }
];
export default function RotateVideo({ title }: ToolComponentProps) {
const { t } = useTranslation();
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -55,13 +57,13 @@ export default function RotateVideo({ title }: ToolComponentProps) {
updateField
}) => [
{
title: 'Rotation',
title: t('video.rotate.rotation'),
component: (
<Box>
{angleOptions.map((angleOption) => (
<SimpleRadio
key={angleOption.value}
title={angleOption.label}
title={t(`video.rotate.${angleOption.value}Degrees`)}
checked={values.rotation === angleOption.value}
onClick={() => {
updateField('rotation', angleOption.value);
@@ -81,20 +83,20 @@ export default function RotateVideo({ title }: ToolComponentProps) {
<ToolVideoInput
value={input}
onChange={setInput}
title={'Input Video'}
title={t('video.rotate.inputTitle')}
/>
}
resultComponent={
loading ? (
<ToolFileResult
title={'Rotating Video'}
title={t('video.rotate.rotatingVideo')}
value={null}
loading={true}
extension={''}
/>
) : (
<ToolFileResult
title={'Rotated Video'}
title={t('video.rotate.resultTitle')}
value={result}
extension={'mp4'}
/>

View File

@@ -4,10 +4,15 @@ import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Rotate Video',
path: 'rotate',
icon: 'mdi:rotate-right',
icon: 'material-symbols:rotate-right',
description:
'This online utility lets you rotate videos by 90, 180, or 270 degrees. You can preview the rotated video before processing. Supports common video formats like MP4, WebM, and OGG.',
shortDescription: 'Rotate videos by 90, 180, or 270 degrees',
keywords: ['rotate', 'video', 'flip', 'edit', 'adjust'],
component: lazy(() => import('./index'))
'Rotate video files by 90, 180, or 270 degrees. Correct video orientation or create special effects with precise rotation control.',
shortDescription: 'Rotate video by specified degrees',
keywords: ['video', 'rotate', 'orientation', 'degrees'],
component: lazy(() => import('./index')),
i18n: {
name: 'video.rotate.name',
description: 'video.rotate.description',
shortDescription: 'video.rotate.shortDescription'
}
});

View File

@@ -11,6 +11,7 @@ import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile } from '@ffmpeg/util';
import { debounce } from 'lodash';
import ToolVideoInput from '@components/input/ToolVideoInput';
import { useTranslation } from 'react-i18next';
const ffmpeg = new FFmpeg();
@@ -28,6 +29,7 @@ const validationSchema = Yup.object({
});
export default function TrimVideo({ title }: ToolComponentProps) {
const { t } = useTranslation();
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
@@ -85,7 +87,7 @@ export default function TrimVideo({ title }: ToolComponentProps) {
updateField
}) => [
{
title: 'Timestamps',
title: t('video.trim.timestamps'),
component: (
<Box>
<TextFieldWithDesc
@@ -93,7 +95,7 @@ export default function TrimVideo({ title }: ToolComponentProps) {
updateNumberField(value, 'trimStart', updateField)
}
value={values.trimStart}
label={'Start Time'}
label={t('video.trim.startTime')}
sx={{ mb: 2, backgroundColor: 'background.paper' }}
/>
<TextFieldWithDesc
@@ -101,7 +103,7 @@ export default function TrimVideo({ title }: ToolComponentProps) {
updateNumberField(value, 'trimEnd', updateField)
}
value={values.trimEnd}
label={'End Time'}
label={t('video.trim.endTime')}
/>
</Box>
)
@@ -116,7 +118,7 @@ export default function TrimVideo({ title }: ToolComponentProps) {
<ToolVideoInput
value={input}
onChange={setInput}
title={'Input Video'}
title={t('video.trim.inputTitle')}
showTrimControls={true}
onTrimChange={(trimStart, trimEnd) => {
setFieldValue('trimStart', trimStart);
@@ -129,7 +131,7 @@ export default function TrimVideo({ title }: ToolComponentProps) {
}}
resultComponent={
<ToolFileResult
title={'Trimmed Video'}
title={t('video.trim.resultTitle')}
value={result}
extension={'mp4'}
/>

View File

@@ -4,10 +4,15 @@ import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Trim Video',
path: 'trim',
icon: 'mdi:scissors',
icon: 'material-symbols:content-cut',
description:
'This online utility lets you trim videos by setting start and end points. You can preview the trimmed section before processing. Supports common video formats like MP4, WebM, and OGG.',
shortDescription: 'Trim videos by setting start and end points',
keywords: ['trim', 'cut', 'video', 'clip', 'edit'],
component: lazy(() => import('./index'))
'Trim video files by specifying start and end times. Remove unwanted sections from the beginning or end of videos.',
shortDescription: 'Trim video by removing unwanted sections',
keywords: ['video', 'trim', 'cut', 'edit', 'time'],
component: lazy(() => import('./index')),
i18n: {
name: 'video.trim.name',
description: 'video.trim.description',
shortDescription: 'video.trim.shortDescription'
}
});

View File

@@ -2,11 +2,17 @@ import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('video', {
name: 'Video to Gif',
name: 'Video to GIF',
path: 'video-to-gif',
icon: 'fluent:gif-16-regular',
description: 'This online utility lets you convert a short video to gif.',
shortDescription: 'Quickly convert a short video to gif',
keywords: ['video', 'to', 'gif', 'convert'],
component: lazy(() => import('./index'))
icon: 'material-symbols:gif',
description:
'Convert video files to animated GIF format. Extract specific time ranges and create shareable animated images.',
shortDescription: 'Convert video to animated GIF',
keywords: ['video', 'gif', 'convert', 'animated', 'image'],
component: lazy(() => import('./index')),
i18n: {
name: 'video.videoToGif.name',
description: 'video.videoToGif.description',
shortDescription: 'video.videoToGif.shortDescription'
}
});