mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-11-05 16:34:57 +05:30
feat: add internationalization support
This commit is contained in:
parent
3b702b260c
commit
f22bb8bd57
149 changed files with 2807 additions and 1045 deletions
111
package-lock.json
generated
111
package-lock.json
generated
|
|
@ -24,6 +24,7 @@
|
|||
"@types/lodash": "^4.17.5",
|
||||
"@types/morsee": "^1.0.2",
|
||||
"@types/omggif": "^1.0.5",
|
||||
"@types/react-i18next": "^7.8.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"buffer": "^6.0.3",
|
||||
"color": "^4.2.3",
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
"dayjs": "^1.11.13",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"formik": "^2.4.6",
|
||||
"i18next": "^25.3.2",
|
||||
"jimp": "^0.22.12",
|
||||
"js-quantities": "^1.8.0",
|
||||
"jszip": "^3.10.1",
|
||||
|
|
@ -51,6 +53,7 @@
|
|||
"react-dom": "^18.3.1",
|
||||
"react-filerobot-image-editor": "^4.9.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.6.0",
|
||||
"react-image-crop": "^11.0.7",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-router-dom": "^6.23.1",
|
||||
|
|
@ -302,12 +305,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
|
||||
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
|
|
@ -3395,6 +3396,12 @@
|
|||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/i18next": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/i18next/-/i18next-12.1.0.tgz",
|
||||
"integrity": "sha512-qLyqTkp3ZKHsSoX8CNVYcTyTkxlm0aRCUpaUVetgkSlSpiNCdWryOgaYwgbO04tJIfLgBXPcy0tJ3Nl/RagllA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/js-quantities": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-quantities/-/js-quantities-1.6.6.tgz",
|
||||
|
|
@ -3484,6 +3491,16 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-i18next": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-i18next/-/react-i18next-7.8.3.tgz",
|
||||
"integrity": "sha512-VPopxbHXz/1Sjl+ljXQQchf6FHXaYLaH0a6TH6KnGOQGD4LzNbUVlofK26S30OIYfYibm8r/sAb2KeTst+AwTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/i18next": "*",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-reconciler": {
|
||||
"version": "0.28.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
|
||||
|
|
@ -6684,6 +6701,15 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||
|
|
@ -6707,6 +6733,37 @@
|
|||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.3.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.2.tgz",
|
||||
"integrity": "sha512-JSnbZDxRVbphc5jiptxr3o2zocy5dEqpVm9qCGdJwRNO+9saUJS0/u4LnM/13C23fUEWxAylPqKU/NpMV/IjqA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
|
@ -9682,6 +9739,32 @@
|
|||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.0.tgz",
|
||||
"integrity": "sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-image-crop": {
|
||||
"version": "11.0.7",
|
||||
"resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.7.tgz",
|
||||
|
|
@ -9880,11 +9963,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
||||
|
|
@ -11347,7 +11425,7 @@
|
|||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -11631,6 +11709,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
"@types/lodash": "^4.17.5",
|
||||
"@types/morsee": "^1.0.2",
|
||||
"@types/omggif": "^1.0.5",
|
||||
"@types/react-i18next": "^7.8.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"buffer": "^6.0.3",
|
||||
"color": "^4.2.3",
|
||||
|
|
@ -49,6 +50,7 @@
|
|||
"dayjs": "^1.11.13",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"formik": "^2.4.6",
|
||||
"i18next": "^25.3.2",
|
||||
"jimp": "^0.22.12",
|
||||
"js-quantities": "^1.8.0",
|
||||
"jszip": "^3.10.1",
|
||||
|
|
@ -68,6 +70,7 @@
|
|||
"react-dom": "^18.3.1",
|
||||
"react-filerobot-image-editor": "^4.9.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.6.0",
|
||||
"react-image-crop": "^11.0.7",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-router-dom": "^6.23.1",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { useNavigate } from 'react-router-dom';
|
|||
import _ from 'lodash';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { getToolCategoryTitle } from '@utils/string';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const GroupHeader = styled('div')(({ theme }) => ({
|
||||
position: 'sticky',
|
||||
|
|
@ -48,6 +49,7 @@ const exampleTools: { label: string; url: string }[] = [
|
|||
{ label: 'Calculate number sum', url: '/number/sum' }
|
||||
];
|
||||
export default function Hero() {
|
||||
const { t } = useTranslation();
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const theme = useTheme();
|
||||
const [filteredTools, setFilteredTools] = useState<DefinedTool[]>(tools);
|
||||
|
|
@ -64,13 +66,13 @@ export default function Hero() {
|
|||
<Box width={{ xs: '90%', md: '80%', lg: '60%' }}>
|
||||
<Stack mb={1} direction={'row'} spacing={1} justifyContent={'center'}>
|
||||
<Typography sx={{ textAlign: 'center' }} fontSize={{ xs: 25, md: 30 }}>
|
||||
Get Things Done Quickly with{' '}
|
||||
{t('hero.title')}{' '}
|
||||
<Typography
|
||||
fontSize={{ xs: 25, md: 30 }}
|
||||
display={'inline'}
|
||||
color={'primary'}
|
||||
>
|
||||
OmniTools
|
||||
{t('hero.brand')}
|
||||
</Typography>
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
|
@ -79,9 +81,7 @@ export default function Hero() {
|
|||
fontSize={{ xs: 15, md: 20 }}
|
||||
mb={2}
|
||||
>
|
||||
Boost your productivity with OmniTools, the ultimate toolkit for getting
|
||||
things done quickly! Access thousands of user-friendly utilities for
|
||||
editing images, text, lists, and data, all directly from your browser.
|
||||
{t('hero.description')}
|
||||
</Typography>
|
||||
|
||||
<Autocomplete
|
||||
|
|
@ -103,7 +103,7 @@ export default function Hero() {
|
|||
<TextField
|
||||
{...params}
|
||||
fullWidth
|
||||
placeholder={'Search all tools'}
|
||||
placeholder={t('hero.searchPlaceholder')}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: <SearchIcon />,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
|||
import { useTheme } from '@mui/material/styles';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { Mode } from 'components/App';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface NavbarProps {
|
||||
mode: Mode;
|
||||
|
|
@ -29,6 +30,7 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
mode,
|
||||
onChangeMode: onChangeMode
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
|
@ -83,7 +85,7 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
/>
|
||||
}
|
||||
>
|
||||
Buy me a coffee
|
||||
{t('navbar.buyMeACoffee')}
|
||||
</Button>
|
||||
];
|
||||
const drawerList = (
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Icon, IconifyIcon } from '@iconify/react';
|
|||
import { categoriesColors } from '../config/uiConfig';
|
||||
import { getToolsByCategory } from '@tools/index';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const StyledButton = styled(Button)(({ theme }) => ({
|
||||
backgroundColor: 'white',
|
||||
|
|
@ -24,6 +25,7 @@ interface ToolHeaderProps {
|
|||
}
|
||||
|
||||
function ToolLinks() {
|
||||
const { t } = useTranslation();
|
||||
const [examplesVisible, setExamplesVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -63,7 +65,7 @@ function ToolLinks() {
|
|||
sx={{ backgroundColor: 'background.paper' }}
|
||||
onClick={() => scrollToElement('examples')}
|
||||
>
|
||||
See Examples
|
||||
{t('toolHeader.seeExamples')}
|
||||
</StyledButton>
|
||||
</Grid>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,20 +7,33 @@ import AllTools from './allTools/AllTools';
|
|||
import { getToolsByCategory } from '@tools/index';
|
||||
import { capitalizeFirstLetter } from '../utils/string';
|
||||
import { IconifyIcon } from '@iconify/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ToolLayout({
|
||||
children,
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
type
|
||||
type,
|
||||
i18n
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
icon?: IconifyIcon | string;
|
||||
type: string;
|
||||
children: ReactNode;
|
||||
i18n?: {
|
||||
name: string;
|
||||
description: string;
|
||||
shortDescription: string;
|
||||
};
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Use i18n keys if available, otherwise fall back to provided strings
|
||||
const toolTitle = i18n ? t(i18n.name) : title;
|
||||
const toolDescription = i18n ? t(i18n.description) : description;
|
||||
|
||||
const otherCategoryTools =
|
||||
getToolsByCategory()
|
||||
.find((category) => category.type === type)
|
||||
|
|
@ -41,22 +54,24 @@ export default function ToolLayout({
|
|||
sx={{ backgroundColor: 'background.default' }}
|
||||
>
|
||||
<Helmet>
|
||||
<title>{`${title} - OmniTools`}</title>
|
||||
<title>{`${toolTitle} - OmniTools`}</title>
|
||||
</Helmet>
|
||||
<Box width={'85%'}>
|
||||
<ToolHeader
|
||||
title={title}
|
||||
description={description}
|
||||
title={toolTitle}
|
||||
description={toolDescription}
|
||||
icon={icon}
|
||||
type={type}
|
||||
/>
|
||||
{children}
|
||||
<Separator backgroundColor="#5581b5" margin="50px" />
|
||||
<AllTools
|
||||
title={`All ${capitalizeFirstLetter(
|
||||
getToolsByCategory().find((category) => category.type === type)!
|
||||
.rawTitle
|
||||
)} tools`}
|
||||
title={t('toolLayout.allToolsTitle', {
|
||||
type: capitalizeFirstLetter(
|
||||
getToolsByCategory().find((category) => category.type === type)!
|
||||
.rawTitle
|
||||
)
|
||||
})}
|
||||
toolCards={otherCategoryTools}
|
||||
/>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import ExampleCard, { ExampleCardProps } from './ExampleCard';
|
|||
import React from 'react';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type CardExampleType<T> = Omit<
|
||||
ExampleCardProps<T>,
|
||||
|
|
@ -24,6 +25,7 @@ export default function ToolExamples<T>({
|
|||
getGroups,
|
||||
setInput
|
||||
}: ExampleProps<T>) {
|
||||
const { t } = useTranslation();
|
||||
const { setValues } = useFormikContext<T>();
|
||||
|
||||
function changeInputResult(newInput: string | undefined, newOptions: T) {
|
||||
|
|
@ -39,10 +41,10 @@ export default function ToolExamples<T>({
|
|||
<Box id={'examples'} mt={4}>
|
||||
<Box mt={4} display="flex" gap={1} alignItems="center">
|
||||
<Typography mb={2} fontSize={30} color={'primary'}>
|
||||
{`${title} Examples`}
|
||||
{t('toolExamples.title', { title })}
|
||||
</Typography>
|
||||
<Typography mb={2} fontSize={30} color={'secondary'}>
|
||||
{subtitle ?? 'Click to try!'}
|
||||
{subtitle ?? t('toolExamples.subtitle')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { globalInputHeight } from '../../config/uiConfig';
|
|||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
import greyPattern from '@assets/grey-pattern.png';
|
||||
import { isArray } from 'lodash';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface BaseFileInputComponentProps extends BaseFileInputProps {
|
||||
children: (props: { preview: string | undefined }) => ReactNode;
|
||||
|
|
@ -26,6 +27,7 @@ export default function BaseFileInput({
|
|||
children,
|
||||
type
|
||||
}: BaseFileInputComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [preview, setPreview] = useState<string | null>(null);
|
||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
|
|
@ -60,9 +62,9 @@ export default function BaseFileInput({
|
|||
|
||||
navigator.clipboard
|
||||
.write([clipboardItem])
|
||||
.then(() => showSnackBar('File copied', 'success'))
|
||||
.then(() => showSnackBar(t('baseFileInput.fileCopied'), 'success'))
|
||||
.catch((err) => {
|
||||
showSnackBar('Failed to copy: ' + err, 'error');
|
||||
showSnackBar(t('baseFileInput.copyFailed', { error: err }), 'error');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -190,7 +192,7 @@ export default function BaseFileInput({
|
|||
variant="h6"
|
||||
align="center"
|
||||
>
|
||||
Drop your {type} here
|
||||
{t('baseFileInput.dropFileHere', { type })}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography
|
||||
|
|
@ -200,9 +202,7 @@ export default function BaseFileInput({
|
|||
: theme.palette.grey['600']
|
||||
}
|
||||
>
|
||||
Click here to select a {type} from your device, press Ctrl+V to
|
||||
use a {type} from your clipboard, or drag and drop a file from
|
||||
desktop
|
||||
{t('baseFileInput.selectFileDescription', { type })}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Button from '@mui/material/Button';
|
|||
import PublishIcon from '@mui/icons-material/Publish';
|
||||
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function InputFooter({
|
||||
handleImport,
|
||||
|
|
@ -13,19 +14,21 @@ export default function InputFooter({
|
|||
handleCopy?: () => void;
|
||||
handleClear?: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack mt={1} direction={'row'} spacing={2}>
|
||||
<Button onClick={handleImport} startIcon={<PublishIcon />}>
|
||||
Import from file
|
||||
{t('inputFooter.importFromFile')}
|
||||
</Button>
|
||||
{handleCopy && (
|
||||
<Button onClick={handleCopy} startIcon={<ContentPasteIcon />}>
|
||||
Copy to clipboard
|
||||
{t('inputFooter.copyToClipboard')}
|
||||
</Button>
|
||||
)}
|
||||
{handleClear && (
|
||||
<Button onClick={handleClear} startIcon={<ClearIcon />}>
|
||||
Clear
|
||||
{t('inputFooter.clear')}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||
import { Grid, Select, MenuItem } from '@mui/material';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import Qty from 'js-quantities';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
//
|
||||
|
||||
const siPrefixes: { [key: string]: number } = {
|
||||
|
|
@ -23,6 +24,7 @@ export default function NumericInputWithUnit(props: {
|
|||
onOwnChange?: (value: { value: number; unit: string }) => void;
|
||||
defaultPrefix?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [inputValue, setInputValue] = useState(props.value.value);
|
||||
const [prefix, setPrefix] = useState(props.defaultPrefix || 'Default prefix');
|
||||
|
||||
|
|
@ -158,7 +160,7 @@ export default function NumericInputWithUnit(props: {
|
|||
<Select
|
||||
fullWidth
|
||||
disabled={disableChangingUnit}
|
||||
placeholder={'Unit'}
|
||||
placeholder={t('numericInputWithUnit.unit')}
|
||||
sx={{ width: { xs: '75%', sm: '80%', md: '90%' } }}
|
||||
value={unit}
|
||||
onChange={(event) => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import InputFooter from './InputFooter';
|
|||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
import { isArray } from 'lodash';
|
||||
import MusicNoteIcon from '@mui/icons-material/MusicNote';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface MultiAudioInputComponentProps {
|
||||
accept: string[];
|
||||
|
|
@ -27,7 +28,10 @@ export default function ToolMultipleAudioInput({
|
|||
title,
|
||||
type
|
||||
}: MultiAudioInputComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = event.target.files;
|
||||
|
|
@ -93,7 +97,12 @@ export default function ToolMultipleAudioInput({
|
|||
return (
|
||||
<Box>
|
||||
<InputHeader
|
||||
title={title || 'Input ' + type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
title={
|
||||
title ||
|
||||
t('toolMultipleAudioInput.inputTitle', {
|
||||
type: type.charAt(0).toUpperCase() + type.slice(1)
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -152,7 +161,7 @@ export default function ToolMultipleAudioInput({
|
|||
))
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No files selected
|
||||
{t('toolMultipleAudioInput.noFilesSelected')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import InputFooter from './InputFooter';
|
|||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
import { isArray } from 'lodash';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface MultiPdfInputComponentProps {
|
||||
accept: string[];
|
||||
|
|
@ -27,6 +28,7 @@ export default function ToolMultiFileInput({
|
|||
title,
|
||||
type
|
||||
}: MultiPdfInputComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
|
|
@ -96,7 +98,12 @@ export default function ToolMultiFileInput({
|
|||
return (
|
||||
<Box>
|
||||
<InputHeader
|
||||
title={title || 'Input ' + type.charAt(0).toUpperCase() + type.slice(1)}
|
||||
title={
|
||||
title ||
|
||||
t('toolMultiplePdfInput.inputTitle', {
|
||||
type: type.charAt(0).toUpperCase() + type.slice(1)
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -156,7 +163,7 @@ export default function ToolMultiFileInput({
|
|||
))
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
No files selected
|
||||
{t('toolMultiplePdfInput.noFilesSelected')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import React, { useContext, useRef } from 'react';
|
|||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
import InputHeader from '../InputHeader';
|
||||
import InputFooter from './InputFooter';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ToolTextInput({
|
||||
value,
|
||||
|
|
@ -15,15 +16,16 @@ export default function ToolTextInput({
|
|||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard
|
||||
.writeText(value)
|
||||
.then(() => showSnackBar('Text copied', 'success'))
|
||||
.then(() => showSnackBar(t('toolTextInput.copied'), 'success'))
|
||||
.catch((err) => {
|
||||
showSnackBar('Failed to copy: ' + err, 'error');
|
||||
showSnackBar(t('toolTextInput.copyFailed', { error: err }), 'error');
|
||||
});
|
||||
};
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
@ -45,14 +47,14 @@ export default function ToolTextInput({
|
|||
};
|
||||
return (
|
||||
<Box>
|
||||
<InputHeader title={title} />
|
||||
<InputHeader title={title || t('toolTextInput.input')} />
|
||||
<TextField
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
fullWidth
|
||||
multiline
|
||||
rows={10}
|
||||
placeholder={placeholder}
|
||||
placeholder={placeholder || t('toolTextInput.placeholder')}
|
||||
sx={{
|
||||
'&.MuiTextField-root': {
|
||||
backgroundColor: 'background.paper'
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Typography from '@mui/material/Typography';
|
|||
import React, { ReactNode } from 'react';
|
||||
import { FormikProps, FormikValues, useFormikContext } from 'formik';
|
||||
import ToolOptionGroups, { ToolOptionGroup } from './ToolOptionGroups';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type UpdateField<T> = <Y extends keyof T>(field: Y, value: T[Y]) => void;
|
||||
type NonEmptyArray<T> = [T, ...T[]];
|
||||
|
|
@ -20,6 +21,7 @@ export default function ToolOptions<T extends FormikValues>({
|
|||
getGroups: GetGroupsType<T> | null;
|
||||
vertical?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const formikContext = useFormikContext<T>();
|
||||
|
||||
|
|
@ -45,7 +47,7 @@ export default function ToolOptions<T extends FormikValues>({
|
|||
>
|
||||
<Stack direction={'row'} spacing={1} alignItems={'center'}>
|
||||
<SettingsIcon />
|
||||
<Typography fontSize={22}>Tool options</Typography>
|
||||
<Typography fontSize={22}>{t('toolOptions.title')}</Typography>
|
||||
</Stack>
|
||||
<Box mt={2}>
|
||||
<Stack direction={'row'} spacing={2}>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@ import Button from '@mui/material/Button';
|
|||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ResultFooter({
|
||||
handleDownload,
|
||||
handleCopy,
|
||||
disabled,
|
||||
hideCopy,
|
||||
downloadLabel = 'Download'
|
||||
downloadLabel
|
||||
}: {
|
||||
handleDownload: () => void;
|
||||
handleCopy?: () => void;
|
||||
|
|
@ -17,6 +18,7 @@ export default function ResultFooter({
|
|||
hideCopy?: boolean;
|
||||
downloadLabel?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Stack mt={1} direction={'row'} spacing={2}>
|
||||
<Button
|
||||
|
|
@ -24,7 +26,7 @@ export default function ResultFooter({
|
|||
onClick={handleDownload}
|
||||
startIcon={<DownloadIcon />}
|
||||
>
|
||||
{downloadLabel}
|
||||
{downloadLabel || t('resultFooter.download')}
|
||||
</Button>
|
||||
{!hideCopy && (
|
||||
<Button
|
||||
|
|
@ -32,7 +34,7 @@ export default function ResultFooter({
|
|||
onClick={handleCopy}
|
||||
startIcon={<ContentPasteIcon />}
|
||||
>
|
||||
Copy to clipboard
|
||||
{t('resultFooter.copy')}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import greyPattern from '@assets/grey-pattern.png';
|
|||
import { globalInputHeight } from '../../config/uiConfig';
|
||||
import ResultFooter from './ResultFooter';
|
||||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ToolFileResult({
|
||||
title = 'Result',
|
||||
|
|
@ -19,6 +20,7 @@ export default function ToolFileResult({
|
|||
loading?: boolean;
|
||||
loadingText?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [preview, setPreview] = React.useState<string | null>(null);
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
const theme = useTheme();
|
||||
|
|
@ -41,9 +43,9 @@ export default function ToolFileResult({
|
|||
|
||||
navigator.clipboard
|
||||
.write([clipboardItem])
|
||||
.then(() => showSnackBar('File copied', 'success'))
|
||||
.then(() => showSnackBar(t('toolFileResult.copied'), 'success'))
|
||||
.catch((err) => {
|
||||
showSnackBar('Failed to copy: ' + err, 'error');
|
||||
showSnackBar(t('toolFileResult.copyFailed', { error: err }), 'error');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -91,7 +93,7 @@ export default function ToolFileResult({
|
|||
|
||||
return (
|
||||
<Box>
|
||||
<InputHeader title={title} />
|
||||
<InputHeader title={title || t('toolFileResult.result')} />
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
|
|
@ -114,7 +116,7 @@ export default function ToolFileResult({
|
|||
>
|
||||
<CircularProgress />
|
||||
<Typography variant="body2" sx={{ mt: 2 }}>
|
||||
{loadingText}... This may take a moment.
|
||||
{loadingText || t('toolFileResult.loading')}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ import InputHeader from '../InputHeader';
|
|||
import greyPattern from '@assets/grey-pattern.png';
|
||||
import { globalInputHeight } from '../../config/uiConfig';
|
||||
import ResultFooter from './ResultFooter';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useContext } from 'react';
|
||||
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
|
||||
|
||||
export default function ToolFileResult({
|
||||
export default function ToolMultiFileResult({
|
||||
title = 'Result',
|
||||
value,
|
||||
zipFile,
|
||||
|
|
@ -23,7 +26,9 @@ export default function ToolFileResult({
|
|||
loading?: boolean;
|
||||
loadingText?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
|
||||
const getFileType = (
|
||||
file: File
|
||||
|
|
@ -46,9 +51,25 @@ export default function ToolFileResult({
|
|||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
if (zipFile) {
|
||||
const blob = new Blob([zipFile], { type: zipFile.type });
|
||||
const clipboardItem = new ClipboardItem({ [zipFile.type]: blob });
|
||||
navigator.clipboard
|
||||
.write([clipboardItem])
|
||||
.then(() => showSnackBar(t('toolMultiFileResult.copied'), 'success'))
|
||||
.catch((err) => {
|
||||
showSnackBar(
|
||||
t('toolMultiFileResult.copyFailed', { error: err }),
|
||||
'error'
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<InputHeader title={title} />
|
||||
<InputHeader title={title || t('toolMultiFileResult.result')} />
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
|
|
@ -77,7 +98,7 @@ export default function ToolFileResult({
|
|||
>
|
||||
<CircularProgress />
|
||||
<Typography variant="body2" sx={{ mt: 2 }}>
|
||||
{loadingText}... This may take a moment.
|
||||
{loadingText || t('toolMultiFileResult.loading')}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import ResultFooter from './ResultFooter';
|
|||
import { replaceSpecialCharacters } from '@utils/string';
|
||||
import mime from 'mime';
|
||||
import { globalInputHeight } from '../../config/uiConfig';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ToolTextResult({
|
||||
title = 'Result',
|
||||
|
|
@ -20,13 +21,14 @@ export default function ToolTextResult({
|
|||
keepSpecialCharacters?: boolean;
|
||||
loading?: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { showSnackBar } = useContext(CustomSnackBarContext);
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard
|
||||
.writeText(value)
|
||||
.then(() => showSnackBar('Text copied', 'success'))
|
||||
.then(() => showSnackBar(t('toolTextResult.copied'), 'success'))
|
||||
.catch((err) => {
|
||||
showSnackBar('Failed to copy: ' + err, 'error');
|
||||
showSnackBar(t('toolTextResult.copyFailed', { error: err }), 'error');
|
||||
});
|
||||
};
|
||||
const handleDownload = () => {
|
||||
|
|
@ -48,7 +50,7 @@ export default function ToolTextResult({
|
|||
};
|
||||
return (
|
||||
<Box>
|
||||
<InputHeader title={title} />
|
||||
<InputHeader title={title || t('toolTextResult.result')} />
|
||||
{loading ? (
|
||||
<Box
|
||||
sx={{
|
||||
|
|
@ -61,7 +63,7 @@ export default function ToolTextResult({
|
|||
>
|
||||
<CircularProgress />
|
||||
<Typography variant="body2" sx={{ mt: 2 }}>
|
||||
Loading... This may take a moment.
|
||||
{t('toolTextResult.loading')}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
|
|
|
|||
187
src/i18n/en.json
Normal file
187
src/i18n/en.json
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
{
|
||||
"app": {
|
||||
"title": "Omni Tools",
|
||||
"language": "Language"
|
||||
},
|
||||
"navbar": {
|
||||
"home": "Home",
|
||||
"tools": "Tools",
|
||||
"buyMeACoffee": "Buy me a coffee"
|
||||
},
|
||||
"resultFooter": {
|
||||
"download": "Download",
|
||||
"copy": "Copy to clipboard"
|
||||
},
|
||||
"toolTextResult": {
|
||||
"result": "Result",
|
||||
"loading": "Loading... This may take a moment.",
|
||||
"copied": "Text copied",
|
||||
"copyFailed": "Failed to copy: {{error}}"
|
||||
},
|
||||
"toolFileResult": {
|
||||
"result": "Result",
|
||||
"loading": "Loading... This may take a moment.",
|
||||
"copied": "File copied",
|
||||
"copyFailed": "Failed to copy: {{error}}"
|
||||
},
|
||||
"toolMultiFileResult": {
|
||||
"result": "Result",
|
||||
"loading": "Loading... This may take a moment.",
|
||||
"copied": "File copied",
|
||||
"copyFailed": "Failed to copy: {{error}}"
|
||||
},
|
||||
"toolTextInput": {
|
||||
"input": "Input text",
|
||||
"placeholder": "Enter your text here...",
|
||||
"copied": "Text copied",
|
||||
"copyFailed": "Failed to copy: {{error}}"
|
||||
},
|
||||
"toolOptions": {
|
||||
"title": "Tool options"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Get Things Done Quickly with",
|
||||
"brand": "OmniTools",
|
||||
"description": "Boost your productivity with OmniTools, the ultimate toolkit for getting things done quickly! Access thousands of user-friendly utilities for editing images, text, lists, and data, all directly from your browser.",
|
||||
"searchPlaceholder": "Search all tools"
|
||||
},
|
||||
"toolHeader": {
|
||||
"seeExamples": "See Examples"
|
||||
},
|
||||
"toolExamples": {
|
||||
"title": "{{title}} Examples",
|
||||
"subtitle": "Click to try!"
|
||||
},
|
||||
"inputFooter": {
|
||||
"importFromFile": "Import from file",
|
||||
"copyToClipboard": "Copy to clipboard",
|
||||
"clear": "Clear"
|
||||
},
|
||||
"baseFileInput": {
|
||||
"fileCopied": "File copied",
|
||||
"copyFailed": "Failed to copy: {{error}}",
|
||||
"dropFileHere": "Drop your {{type}} here",
|
||||
"selectFileDescription": "Click here to select a {{type}} from your device, press Ctrl+V to use a {{type}} from your clipboard, or drag and drop a file from desktop"
|
||||
},
|
||||
"toolMultiplePdfInput": {
|
||||
"noFilesSelected": "No files selected",
|
||||
"inputTitle": "Input {{type}}"
|
||||
},
|
||||
"toolMultipleAudioInput": {
|
||||
"noFilesSelected": "No files selected",
|
||||
"inputTitle": "Input {{type}}"
|
||||
},
|
||||
"numericInputWithUnit": {
|
||||
"unit": "Unit"
|
||||
},
|
||||
"toolLayout": {
|
||||
"allToolsTitle": "All {{type}} Tools"
|
||||
},
|
||||
"list": {
|
||||
"group": {
|
||||
"name": "Group",
|
||||
"description": "World's simplest browser-based utility for grouping list items. Input your list and specify grouping criteria to organize items into logical groups. Perfect for categorizing data, organizing information, or creating structured lists. Supports custom separators and various grouping options.",
|
||||
"shortDescription": "Group list items by common properties"
|
||||
},
|
||||
"reverse": {
|
||||
"name": "Reverse",
|
||||
"description": "This is a super simple browser-based application prints all list items in reverse. The input items can be separated by any symbol and you can also change the separator of the reversed list items.",
|
||||
"shortDescription": "Quickly reverse a list"
|
||||
},
|
||||
"sort": {
|
||||
"name": "Sort",
|
||||
"description": "This is a super simple browser-based application that sorts items in a list and arranges them in increasing or decreasing order. You can sort the items alphabetically, numerically, or by their length. You can also remove duplicate and empty items, as well as trim individual items that have whitespace around them. You can use any separator character to separate the input list items or alternatively use a regular expression to separate them. Additionally, you can create a new delimiter for the sorted output list.",
|
||||
"shortDescription": "Quickly sort a list"
|
||||
}
|
||||
},
|
||||
"string": {
|
||||
"uppercase": {
|
||||
"name": "Uppercase",
|
||||
"description": "World's simplest browser-based utility for converting text to uppercase. Just input your text and it will be automatically converted to all capital letters. Perfect for creating headlines, emphasizing text, or standardizing text format. Supports various text formats and preserves special characters.",
|
||||
"shortDescription": "Convert text to uppercase letters"
|
||||
},
|
||||
"reverse": {
|
||||
"name": "Reverse",
|
||||
"description": "World's simplest browser-based utility for reversing text. Input any text and get it instantly reversed, character by character. Perfect for creating mirror text, analyzing palindromes, or playing with text patterns. Preserves spaces and special characters while reversing.",
|
||||
"shortDescription": "Reverse any text character by character"
|
||||
},
|
||||
"repeat": {
|
||||
"name": "Repeat text",
|
||||
"description": "This tool allows you to repeat a given text multiple times with an optional separator.",
|
||||
"shortDescription": "Repeat text multiple times"
|
||||
},
|
||||
"createPalindrome": {
|
||||
"name": "Create palindrome",
|
||||
"description": "World's simplest browser-based utility for creating palindromes from any text. Input text and instantly transform it into a palindrome that reads the same forward and backward. Perfect for word games, creating symmetrical text patterns, or exploring linguistic curiosities.",
|
||||
"shortDescription": "Create text that reads the same forward and backward"
|
||||
},
|
||||
"palindrome": {
|
||||
"name": "Palindrome",
|
||||
"description": "World's simplest browser-based utility for checking if text is a palindrome. Instantly verify if your text reads the same forward and backward. Perfect for word puzzles, linguistic analysis, or validating symmetrical text patterns. Supports various delimiters and multi-word palindrome detection.",
|
||||
"shortDescription": "Check if text reads the same forward and backward"
|
||||
},
|
||||
"toMorse": {
|
||||
"name": "String To morse",
|
||||
"description": "World's simplest browser-based utility for converting text to Morse code. Load your text in the input form on the left and you'll instantly get Morse code in the output area. Powerful, free, and fast. Load text – get Morse code.",
|
||||
"shortDescription": "Quickly encode text to morse"
|
||||
}
|
||||
},
|
||||
"pdf": {
|
||||
"mergePdf": {
|
||||
"name": "Merge PDF",
|
||||
"description": "Combine multiple PDF files into a single document.",
|
||||
"shortDescription": "Merge multiple PDF files into a single document"
|
||||
},
|
||||
"pdfToEpub": {
|
||||
"name": "PDF to EPUB",
|
||||
"description": "Transform PDF documents into EPUB files for better e-reader compatibility.",
|
||||
"shortDescription": "Convert PDF files to EPUB format"
|
||||
},
|
||||
"protectPdf": {
|
||||
"name": "Protect PDF",
|
||||
"description": "Add password protection to your PDF files securely in your browser",
|
||||
"shortDescription": "Password protect PDF files securely"
|
||||
},
|
||||
"splitPdf": {
|
||||
"name": "Split PDF",
|
||||
"description": "Extract specific pages from a PDF file using page numbers or ranges (e.g., 1,5-8)",
|
||||
"shortDescription": "Extract specific pages from a PDF file"
|
||||
},
|
||||
"compressPdf": {
|
||||
"name": "Compress PDF",
|
||||
"description": "Reduce PDF file size while maintaining quality using Ghostscript",
|
||||
"shortDescription": "Compress PDF files securely in your browser"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"generate": {
|
||||
"name": "Generate numbers",
|
||||
"description": "Quickly calculate a list of integers in your browser. To get your list, just specify the first integer, change value and total count in the options below, and this utility will generate that many integers",
|
||||
"shortDescription": "Quickly calculate a list of integers in your browser"
|
||||
},
|
||||
"sum": {
|
||||
"name": "Sum numbers",
|
||||
"description": "This is a super simple browser-based application that sums numbers. The input numbers can be separated by any symbol and you can also change the separator of the summed numbers.",
|
||||
"shortDescription": "Quickly sum a list of numbers"
|
||||
}
|
||||
},
|
||||
"audio": {
|
||||
"extractAudio": {
|
||||
"name": "Extract audio",
|
||||
"description": "Extract the audio track from a video file and save it as a separate audio file in your chosen format (AAC, MP3, WAV).",
|
||||
"shortDescription": "Extract audio from video files (MP4, MOV, etc.) to AAC, MP3, or WAV."
|
||||
},
|
||||
"changeSpeed": {
|
||||
"name": "Change audio speed",
|
||||
"description": "Change the playback speed of audio files. Speed up or slow down audio while maintaining pitch.",
|
||||
"shortDescription": "Change the speed of audio files"
|
||||
}
|
||||
},
|
||||
"csv": {
|
||||
"findIncompleteCsvRecords": {
|
||||
"name": "Find incomplete CSV records",
|
||||
"description": "Just upload your CSV file in the form below and this tool will automatically check if none of the rows or columns are missing values. In the tool options, you can adjust the input file format (specify the delimiter, quote character, and comment character). Additionally, you can enable checking for empty values, skip empty lines, and set a limit on the number of error messages in the output.",
|
||||
"shortDescription": "Quickly find rows and columns in CSV that are missing values."
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/i18n/index.ts
Normal file
45
src/i18n/index.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import enGlobal from './en.json';
|
||||
import enList from '../pages/tools/list/i18n/en.json';
|
||||
import enString from '../pages/tools/string/i18n/en.json';
|
||||
import enCsv from '../pages/tools/csv/i18n/en.json';
|
||||
import enJson from '../pages/tools/json/i18n/en.json';
|
||||
import enPdf from '../pages/tools/pdf/i18n/en.json';
|
||||
import enImage from '../pages/tools/image/i18n/en.json';
|
||||
import enAudio from '../pages/tools/audio/i18n/en.json';
|
||||
import enVideo from '../pages/tools/video/i18n/en.json';
|
||||
import enNumber from '../pages/tools/number/i18n/en.json';
|
||||
import enTime from '../pages/tools/time/i18n/en.json';
|
||||
import enXml from '../pages/tools/xml/i18n/en.json';
|
||||
|
||||
// Merge translations for demonstration; in a real app, use namespaces
|
||||
const resources = {
|
||||
en: {
|
||||
translation: {
|
||||
...enGlobal,
|
||||
list: enList,
|
||||
string: enString,
|
||||
csv: enCsv,
|
||||
json: enJson,
|
||||
pdf: enPdf,
|
||||
image: enImage,
|
||||
audio: enAudio,
|
||||
video: enVideo,
|
||||
number: enNumber,
|
||||
time: enTime,
|
||||
xml: enXml
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { createRoot } from 'react-dom/client';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import App from 'components/App';
|
||||
import './i18n';
|
||||
|
||||
const container = document.getElementById('root') as HTMLDivElement;
|
||||
const root = createRoot(container);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import ToolFileResult from '@components/result/ToolFileResult';
|
|||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import RadioWithTextField from '@components/options/RadioWithTextField';
|
||||
import { changeAudioSpeed } from './service';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
newSpeed: 2,
|
||||
|
|
@ -25,6 +26,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);
|
||||
|
|
@ -49,20 +51,20 @@ export default function ChangeSpeed({
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'New Audio Speed',
|
||||
title: t('audio.changeSpeed.newAudioSpeed'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.newSpeed.toString()}
|
||||
onOwnChange={(val) => updateField('newSpeed', Number(val))}
|
||||
description="Default multiplier: 2 means 2x faster"
|
||||
description={t('audio.changeSpeed.speedDescription')}
|
||||
type="number"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Output Format',
|
||||
title: t('audio.changeSpeed.outputFormat'),
|
||||
component: (
|
||||
<Box mt={2}>
|
||||
<RadioGroup
|
||||
|
|
@ -96,15 +98,19 @@ export default function ChangeSpeed({
|
|||
<ToolAudioInput
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
title={'Input Audio'}
|
||||
title={t('audio.changeSpeed.inputTitle')}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
loading ? (
|
||||
<ToolFileResult title="Setting Speed" value={null} loading={true} />
|
||||
<ToolFileResult
|
||||
title={t('audio.changeSpeed.settingSpeed')}
|
||||
value={null}
|
||||
loading={true}
|
||||
/>
|
||||
) : (
|
||||
<ToolFileResult
|
||||
title="Edited Audio"
|
||||
title={t('audio.changeSpeed.resultTitle')}
|
||||
value={result}
|
||||
extension={result ? result.name.split('.').pop() : undefined}
|
||||
/>
|
||||
|
|
@ -114,7 +120,10 @@ export default function ChangeSpeed({
|
|||
getGroups={getGroups}
|
||||
setInput={setInput}
|
||||
compute={compute}
|
||||
toolInfo={{ title: `What is ${title}?`, description: longDescription }}
|
||||
toolInfo={{
|
||||
title: t('audio.changeSpeed.toolInfo.title', { title }),
|
||||
description: longDescription
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,28 @@ import { defineTool } from '@tools/defineTool';
|
|||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('audio', {
|
||||
name: 'Change speed',
|
||||
name: 'Change audio speed',
|
||||
path: 'change-speed',
|
||||
icon: 'material-symbols-light:speed-outline',
|
||||
icon: 'material-symbols:speed',
|
||||
description:
|
||||
'This online utility lets you change the speed of an audio. You can speed it up or slow it down.',
|
||||
shortDescription: 'Quickly change audio speed',
|
||||
keywords: ['change', 'speed'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Change the playback speed of audio files. Speed up or slow down audio while maintaining pitch.',
|
||||
shortDescription: 'Change the speed of audio files',
|
||||
keywords: [
|
||||
'audio',
|
||||
'speed',
|
||||
'tempo',
|
||||
'playback',
|
||||
'accelerate',
|
||||
'slow down',
|
||||
'pitch',
|
||||
'media'
|
||||
],
|
||||
longDescription:
|
||||
'This tool allows you to change the playback speed of audio files. You can speed up or slow down audio while maintaining the original pitch. Useful for podcasts, music, or any audio content where you want to adjust the playback speed.',
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'audio.changeSpeed.name',
|
||||
description: 'audio.changeSpeed.description',
|
||||
shortDescription: 'audio.changeSpeed.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import ToolVideoInput from '@components/input/ToolVideoInput';
|
|||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import ToolFileResult from '@components/result/ToolFileResult';
|
||||
import SelectWithDesc from '@components/options/SelectWithDesc';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
outputFormat: 'aac'
|
||||
|
|
@ -17,6 +18,7 @@ export default function ExtractAudio({
|
|||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [audioFile, setAudioFile] = useState<File | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -27,7 +29,7 @@ export default function ExtractAudio({
|
|||
}) => {
|
||||
return [
|
||||
{
|
||||
title: 'Output Format',
|
||||
title: t('audio.extractAudio.outputFormat'),
|
||||
component: (
|
||||
<Box>
|
||||
<SelectWithDesc
|
||||
|
|
@ -40,9 +42,7 @@ export default function ExtractAudio({
|
|||
{ label: 'MP3', value: 'mp3' },
|
||||
{ label: 'WAV', value: 'wav' }
|
||||
]}
|
||||
description={
|
||||
'Select the format for the audio to be extracted as.'
|
||||
}
|
||||
description={t('audio.extractAudio.outputFormatDescription')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -68,23 +68,33 @@ export default function ExtractAudio({
|
|||
title={title}
|
||||
input={file}
|
||||
inputComponent={
|
||||
<ToolVideoInput value={file} onChange={setFile} title={'Input Video'} />
|
||||
<ToolVideoInput
|
||||
value={file}
|
||||
onChange={setFile}
|
||||
title={t('audio.extractAudio.inputTitle')}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
loading ? (
|
||||
<ToolFileResult
|
||||
title={'Extracting Audio'}
|
||||
title={t('audio.extractAudio.extractingAudio')}
|
||||
value={null}
|
||||
loading={true}
|
||||
/>
|
||||
) : (
|
||||
<ToolFileResult title={'Extracted Audio'} value={audioFile} />
|
||||
<ToolFileResult
|
||||
title={t('audio.extractAudio.resultTitle')}
|
||||
value={audioFile}
|
||||
/>
|
||||
)
|
||||
}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
compute={compute}
|
||||
toolInfo={{ title: `What is ${title}?`, description: longDescription }}
|
||||
toolInfo={{
|
||||
title: t('audio.extractAudio.toolInfo.title', { title }),
|
||||
description: longDescription
|
||||
}}
|
||||
setInput={setFile}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,5 +22,10 @@ export const tool = defineTool('audio', {
|
|||
],
|
||||
longDescription:
|
||||
'This tool allows you to extract the audio track from a video file (such as MP4, MOV, AVI, etc.) and save it as a standalone audio file in your preferred format (AAC, MP3, or WAV). Useful for podcasts, music, or any scenario where you need just the audio from a video.',
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'audio.extractAudio.name',
|
||||
description: 'audio.extractAudio.description',
|
||||
shortDescription: 'audio.extractAudio.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
28
src/pages/tools/audio/i18n/en.json
Normal file
28
src/pages/tools/audio/i18n/en.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"changeSpeed": {
|
||||
"title": "Change Audio Speed",
|
||||
"description": "Change the playback speed of audio files.",
|
||||
"inputTitle": "Input Audio",
|
||||
"resultTitle": "Modified Audio",
|
||||
"speedOptions": "Speed Options",
|
||||
"speedDescription": "Speed multiplier (0.5 = half speed, 2.0 = double speed)",
|
||||
"speedPlaceholder": "Speed",
|
||||
"toolInfo": {
|
||||
"title": "Change audio speed",
|
||||
"description": "This tool allows you to change the playback speed of audio files. You can slow down or speed up audio while maintaining pitch quality."
|
||||
}
|
||||
},
|
||||
"extractAudio": {
|
||||
"title": "Extract Audio from Video",
|
||||
"description": "Extract audio track from video files.",
|
||||
"inputTitle": "Input Video",
|
||||
"resultTitle": "Extracted Audio",
|
||||
"outputFormat": "Output Format",
|
||||
"outputFormatDescription": "Select the format for the audio to be extracted as.",
|
||||
"extractingAudio": "Extracting Audio",
|
||||
"toolInfo": {
|
||||
"title": "What is {{title}}?",
|
||||
"description": "This tool allows you to extract the audio track from video files. You can choose from different audio formats including AAC, MP3, and WAV."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,17 @@ import { defineTool } from '@tools/defineTool';
|
|||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('csv', {
|
||||
name: 'Change CSV separator',
|
||||
name: 'Change CSV Separator',
|
||||
path: 'change-csv-separator',
|
||||
icon: 'material-symbols:split-scene-rounded',
|
||||
icon: 'material-symbols:code',
|
||||
description:
|
||||
'Just upload your CSV file in the form below and it will automatically get a new column delimiter character. In the tool options, you can specify which delimiter and quote characters are used in the source CSV file and customize the desired delimiter and quote characters for the output CSV. You can also filter the input CSV before the conversion process and skip blank lines and comment lines.',
|
||||
shortDescription: 'Quickly change the CSV column delimiter to a new symbol.',
|
||||
keywords: ['change', 'csv', 'sepa rator'],
|
||||
longDescription:
|
||||
'This tool changes the field separator in CSV (Comma Separated Values) files. This is useful because different programs may use different default separators. While a comma is the most common separator in CSV files, some programs require files to be tab-separated (TSV), semicolon-separated (SSV), pipe-separated (PSV), or have another separation symbol. The default comma may not be so convenient as a delimiter in CSV files because commas are frequently present within fields. In such cases, it can be difficult and confusing to distinguish between commas as delimiters and commas as punctuation symbols. By replacing the comma with another delimiter, you can convert the file into a more easily readable and parsable format. In the options section of this tool, you can configure both the input and output CSV file formats. For the input CSV, you can specify its current delimiter (by default, it is a comma) and also indicate the quotation mark character used to wrap fields. For the output CSV, you can set a new delimiter, choose a new quotation mark character, and optionally enclose all the fields in quotes. Additionally, you have the option to remove empty lines from the input CSV and eliminate comment lines that start with a specified character (usually a hash "#" or double slashes "//"). Csv-abulous!',
|
||||
component: lazy(() => import('./index'))
|
||||
'Change the delimiter/separator in CSV files. Convert between different CSV formats like comma, semicolon, tab, or custom separators.',
|
||||
shortDescription: 'Change CSV file delimiter',
|
||||
keywords: ['csv', 'separator', 'delimiter', 'change'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'csv.changeCsvSeparator.name',
|
||||
description: 'csv.changeCsvSeparator.description',
|
||||
shortDescription: 'csv.changeCsvSeparator.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@ import { convertCsvToJson } from './service';
|
|||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { Box } from '@mui/material';
|
||||
import RadioWithTextField from '@components/options/RadioWithTextField';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type InitialValuesType = {
|
||||
delimiter: string;
|
||||
|
|
@ -114,6 +113,7 @@ id,name,active
|
|||
];
|
||||
|
||||
export default function CsvToJson({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -148,28 +148,36 @@ export default function CsvToJson({ title }: ToolComponentProps) {
|
|||
compute={compute}
|
||||
exampleCards={exampleCards}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input CSV" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('csv.csvToJson.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title="Output JSON" value={result} extension={'json'} />
|
||||
<ToolTextResult
|
||||
title={t('csv.csvToJson.resultTitle')}
|
||||
value={result}
|
||||
extension={'json'}
|
||||
/>
|
||||
}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Input CSV Format',
|
||||
title: t('csv.csvToJson.inputCsvFormat'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description="Column Separator"
|
||||
description={t('csv.csvToJson.columnSeparator')}
|
||||
value={values.delimiter}
|
||||
onOwnChange={(val) => updateField('delimiter', val)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Field Quote"
|
||||
description={t('csv.csvToJson.fieldQuote')}
|
||||
onOwnChange={(val) => updateField('quote', val)}
|
||||
value={values.quote}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Comment Symbol"
|
||||
description={t('csv.csvToJson.commentSymbol')}
|
||||
value={values.comment}
|
||||
onOwnChange={(val) => updateField('comment', val)}
|
||||
/>
|
||||
|
|
@ -177,26 +185,26 @@ export default function CsvToJson({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Conversion Options',
|
||||
title: t('csv.csvToJson.conversionOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<CheckboxWithDesc
|
||||
checked={values.useHeaders}
|
||||
onChange={(value) => updateField('useHeaders', value)}
|
||||
title="Use Headers"
|
||||
description="First row is treated as column headers"
|
||||
title={t('csv.csvToJson.useHeaders')}
|
||||
description={t('csv.csvToJson.useHeadersDescription')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.skipEmptyLines}
|
||||
onChange={(value) => updateField('skipEmptyLines', value)}
|
||||
title="Skip Empty Lines"
|
||||
description="Don't process empty lines in the CSV"
|
||||
title={t('csv.csvToJson.skipEmptyLines')}
|
||||
description={t('csv.csvToJson.skipEmptyLinesDescription')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.dynamicTypes}
|
||||
onChange={(value) => updateField('dynamicTypes', value)}
|
||||
title="Dynamic Types"
|
||||
description="Convert numbers and booleans to their proper types"
|
||||
title={t('csv.csvToJson.dynamicTypes')}
|
||||
description={t('csv.csvToJson.dynamicTypesDescription')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { findIncompleteCsvRecords } from './service';
|
|||
import { InitialValuesType } from './types';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
csvSeparator: ',',
|
||||
|
|
@ -103,6 +104,7 @@ export default function FindIncompleteCsvRecords({
|
|||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -115,55 +117,59 @@ export default function FindIncompleteCsvRecords({
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Csv input Options',
|
||||
title: t('csv.findIncompleteCsvRecords.csvInputOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.csvSeparator}
|
||||
onOwnChange={(val) => updateField('csvSeparator', val)}
|
||||
description={
|
||||
'Enter the character used to delimit columns in the CSV input file.'
|
||||
}
|
||||
description={t(
|
||||
'csv.findIncompleteCsvRecords.csvSeparatorDescription'
|
||||
)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.quoteCharacter}
|
||||
onOwnChange={(val) => updateField('quoteCharacter', val)}
|
||||
description={
|
||||
'Enter the quote character used to quote the CSV input fields.'
|
||||
}
|
||||
description={t(
|
||||
'csv.findIncompleteCsvRecords.quoteCharacterDescription'
|
||||
)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.commentCharacter}
|
||||
onOwnChange={(val) => updateField('commentCharacter', val)}
|
||||
description={
|
||||
'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'
|
||||
}
|
||||
description={t(
|
||||
'csv.findIncompleteCsvRecords.commentCharacterDescription'
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Checking Options',
|
||||
title: t('csv.findIncompleteCsvRecords.checkingOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<CheckboxWithDesc
|
||||
checked={values.emptyLines}
|
||||
onChange={(value) => updateField('emptyLines', value)}
|
||||
title="Delete Lines with No Data"
|
||||
description="Remove empty lines from CSV input file."
|
||||
title={t('csv.findIncompleteCsvRecords.deleteLinesWithNoData')}
|
||||
description={t(
|
||||
'csv.findIncompleteCsvRecords.deleteLinesWithNoDataDescription'
|
||||
)}
|
||||
/>
|
||||
|
||||
<CheckboxWithDesc
|
||||
checked={values.emptyValues}
|
||||
onChange={(value) => updateField('emptyValues', value)}
|
||||
title="Find Empty Values"
|
||||
description="Display a message about CSV fields that are empty (These are not missing fields but fields that contain nothing)."
|
||||
title={t('csv.findIncompleteCsvRecords.findEmptyValues')}
|
||||
description={t(
|
||||
'csv.findIncompleteCsvRecords.findEmptyValuesDescription'
|
||||
)}
|
||||
/>
|
||||
|
||||
<CheckboxWithDesc
|
||||
checked={values.messageLimit}
|
||||
onChange={(value) => updateField('messageLimit', value)}
|
||||
title="Limit number of messages"
|
||||
title={t('csv.findIncompleteCsvRecords.limitNumberOfMessages')}
|
||||
/>
|
||||
|
||||
{values.messageLimit && (
|
||||
|
|
@ -172,7 +178,9 @@ export default function FindIncompleteCsvRecords({
|
|||
onOwnChange={(val) => updateField('messageNumber', Number(val))}
|
||||
type="number"
|
||||
inputProps={{ min: 1 }}
|
||||
description={'Set the limit of number of messages in the output.'}
|
||||
description={t(
|
||||
'csv.findIncompleteCsvRecords.messageLimitDescription'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
|
@ -184,15 +192,27 @@ export default function FindIncompleteCsvRecords({
|
|||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input CSV'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('csv.findIncompleteCsvRecords.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult
|
||||
title={t('csv.findIncompleteCsvRecords.resultTitle')}
|
||||
value={result}
|
||||
/>
|
||||
}
|
||||
resultComponent={<ToolTextResult title={'CSV Status'} value={result} />}
|
||||
initialValues={initialValues}
|
||||
exampleCards={exampleCards}
|
||||
getGroups={getGroups}
|
||||
setInput={setInput}
|
||||
compute={compute}
|
||||
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
|
||||
toolInfo={{
|
||||
title: t('csv.findIncompleteCsvRecords.toolInfo.title', { title }),
|
||||
description: longDescription
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ export const tool = defineTool('csv', {
|
|||
'Quickly find rows and columns in CSV that are missing values.',
|
||||
keywords: ['find', 'incomplete', 'csv', 'records'],
|
||||
longDescription:
|
||||
'This tool checks the completeness of CSV (Comma Separated Values) files and identifies incomplete records within the data. It finds rows and columns where one or more values are missing and displays their positions in the output so that you can quickly find and fix your CSV file. A valid CSV file has the same number of values (fields) in all rows and the same number of values (fields) in all columns. If the CSV you load in this tool is complete, the program will notify you with a green badge. If at least one value is missing in any row or column, the program will show a red badge and indicate the exact location of the missing value. If the CSV file has a field with no characters in it, then such a field is called an empty field. It is not a missing field, just empty as it contains nothing. You can activate the "Find Empty Values" checkbox in the options to identify all such fields in the CSV. If the file contains empty lines, you can ignore them with the "Skip Empty Lines" option or check them for completeness along with other lines. You can also configure the delimiter, quote, and comment characters in the options. This allows you to adapt to other file formats besides CSV, such as TSV (Tab Separated Values), SSV (Semicolon Separated Values), or PSV (Pipe Separated Values). If the file has too many incomplete or empty records, you can set a limit on the output messages to display, for example, 5, 10, or 20 messages. If you want to quickly fill in the missing data with default values, you can use our Fill Incomplete CSV Records tool. Csv-abulous!',
|
||||
component: lazy(() => import('./index'))
|
||||
'This tool allows you to find incomplete or missing records in CSV data. It can detect missing columns, empty values, and other data quality issues in your CSV files. You can customize the CSV parsing options and set limits on error reporting.',
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'csv.findIncompleteCsvRecords.name',
|
||||
description: 'csv.findIncompleteCsvRecords.description',
|
||||
shortDescription: 'csv.findIncompleteCsvRecords.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
63
src/pages/tools/csv/i18n/en.json
Normal file
63
src/pages/tools/csv/i18n/en.json
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"csvToJson": {
|
||||
"title": "CSV to JSON",
|
||||
"description": "Convert CSV data to JSON format.",
|
||||
"inputTitle": "Input CSV",
|
||||
"resultTitle": "JSON Output",
|
||||
"csvOptions": "CSV Options",
|
||||
"separatorDescription": "Character used to separate columns",
|
||||
"quoteCharDescription": "Character used to quote fields",
|
||||
"commentCharDescription": "Character that indicates comment lines",
|
||||
"jsonOptions": "JSON Options",
|
||||
"prettyPrintDescription": "Format JSON with indentation",
|
||||
"toolInfo": {
|
||||
"title": "CSV to JSON Converter",
|
||||
"description": "This tool allows you to convert CSV (Comma-Separated Values) data to JSON format. You can customize the CSV parsing options and JSON output format."
|
||||
}
|
||||
},
|
||||
"findIncompleteCsvRecords": {
|
||||
"title": "Find Incomplete CSV Records",
|
||||
"description": "Identify CSV records with missing or incomplete data.",
|
||||
"inputTitle": "Input CSV",
|
||||
"resultTitle": "Incomplete Records",
|
||||
"csvOptions": "CSV Options",
|
||||
"separatorDescription": "Character used to separate columns",
|
||||
"quoteCharDescription": "Character used to quote fields",
|
||||
"commentCharDescription": "Character that indicates comment lines",
|
||||
"toolInfo": {
|
||||
"title": "Find Incomplete CSV Records",
|
||||
"description": "This tool helps you identify CSV records that have missing or incomplete data. It's useful for data validation and cleaning."
|
||||
}
|
||||
},
|
||||
"insertCsvColumns": {
|
||||
"title": "Insert CSV Columns",
|
||||
"description": "Add new columns to CSV data at specified positions.",
|
||||
"inputTitle": "Input CSV",
|
||||
"resultTitle": "Output CSV",
|
||||
"csvToInsert": "CSV to insert",
|
||||
"csvSeparator": "CSV separator",
|
||||
"csvToInsertDescription": "Enter one or more columns you want to insert into the CSV. the character used to delimit columns has to be the same with the one in the CSV input file. Ps: Blank lines will be ignored",
|
||||
"csvOptions": "CSV Options",
|
||||
"separatorDescription": "Enter the character used to delimit columns in the CSV input file.",
|
||||
"quoteCharDescription": "Enter the quote character used to quote the CSV input fields.",
|
||||
"commentCharacterDescription": "Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.",
|
||||
"fillWithEmptyValues": "Fill With Empty Values",
|
||||
"fillWithCustomValues": "Fill With Customs Values",
|
||||
"customFillDescription": "If the input CSV file is incomplete (missing values), then add empty fields or custom symbols to records to make a well-formed CSV?",
|
||||
"customFillValueDescription": "Use this custom value to fill in missing fields. (Works only with \"Custom Values\" mode above.)",
|
||||
"positionOptions": "Position Options",
|
||||
"prependColumns": "Prepend columns",
|
||||
"appendColumns": "Append columns",
|
||||
"customPosition": "Custom position",
|
||||
"insertingPositionDescription": "Specify where to insert the columns in the CSV file.",
|
||||
"headerName": "Header name",
|
||||
"position": "Position",
|
||||
"customPositionOptionsDescription": "Select the method to insert the columns in the CSV file.",
|
||||
"headerNameDescription": "Header of the column you want to insert columns after.",
|
||||
"rowNumberDescription": "Number of the column you want to insert columns after.",
|
||||
"toolInfo": {
|
||||
"title": "Insert CSV Columns",
|
||||
"description": "This tool allows you to insert new columns into CSV data at specified positions. You can prepend, append, or insert columns at custom positions based on header names or column numbers."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
import { Box } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolTextInput from '@components/input/ToolTextInput';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { main } from './service';
|
||||
import { getCsvHeaders } from '@utils/csv';
|
||||
import { InitialValuesType } from './types';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import SelectWithDesc from '@components/options/SelectWithDesc';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { getCsvHeaders } from '@utils/csv';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
csvToInsert: '',
|
||||
|
|
@ -27,15 +28,19 @@ const initialValues: InitialValuesType = {
|
|||
|
||||
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
{
|
||||
title: 'Add One Column to a CSV File',
|
||||
title: 'Insert a single column by position',
|
||||
description:
|
||||
'In this example, we insert a column with the title "city" into a CSV file that already contains two other columns with titles "name" and "age". The new column consists of three values: "city", "dallas", and "houston", corresponding to the height of the input CSV data. The value "city" is the header value (appearing on the first row) and values "dallas" and "houston" are data values (appearing on rows two and three). We specify the position of the new column by an ordinal number and set it to 1 in the options. This value indicates that the new "city" column should be placed after the first column.',
|
||||
sampleText: `name,age
|
||||
john,25
|
||||
emma,22`,
|
||||
sampleResult: `name,city,age
|
||||
john,dallas,25
|
||||
emma,houston,22`,
|
||||
'In this example, we insert a single column "city" at position 1 in the CSV data. The input CSV has data about cars, including the "Brand" and "Model" of the car. We now add a "city" column at position 1. To do this, we enter the city data in the comma-separated format in the "New Column" option, and to quickly add the new column at position 1, then we specify the position number.',
|
||||
sampleText: `Brand,Model
|
||||
Toyota,Camry
|
||||
Ford,Mustang
|
||||
Honda,Accord
|
||||
Chevrolet,Malibu`,
|
||||
sampleResult: `city,Brand,Model
|
||||
dallas,Toyota,Camry
|
||||
houston,Ford,Mustang
|
||||
dallas,Honda,Accord
|
||||
houston,Chevrolet,Malibu`,
|
||||
sampleOptions: {
|
||||
csvToInsert: `city
|
||||
dallas
|
||||
|
|
@ -118,6 +123,7 @@ export default function InsertCsvColumns({
|
|||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -139,7 +145,7 @@ export default function InsertCsvColumns({
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'CSV to insert',
|
||||
title: t('csv.insertCsvColumns.csvToInsert'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
|
|
@ -147,44 +153,42 @@ export default function InsertCsvColumns({
|
|||
rows={3}
|
||||
value={values.csvToInsert}
|
||||
onOwnChange={(val) => updateField('csvToInsert', val)}
|
||||
title="CSV separator"
|
||||
description={`Enter one or more columns you want to insert into the CSV.
|
||||
the character used to delimit columns has to be the same with the one in the CSV input file.
|
||||
Ps: Blank lines will be ignored`}
|
||||
title={t('csv.insertCsvColumns.csvSeparator')}
|
||||
description={t('csv.insertCsvColumns.csvToInsertDescription')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'CSV Options',
|
||||
title: t('csv.insertCsvColumns.csvOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.separator}
|
||||
onOwnChange={(val) => updateField('separator', val)}
|
||||
description={
|
||||
'Enter the character used to delimit columns in the CSV input file.'
|
||||
}
|
||||
description={t('csv.insertCsvColumns.separatorDescription')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.quoteChar}
|
||||
onOwnChange={(val) => updateField('quoteChar', val)}
|
||||
description={
|
||||
'Enter the quote character used to quote the CSV input fields.'
|
||||
}
|
||||
description={t('csv.insertCsvColumns.quoteCharDescription')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.commentCharacter}
|
||||
onOwnChange={(val) => updateField('commentCharacter', val)}
|
||||
description={
|
||||
'Enter the character indicating the start of a comment line. Lines starting with this symbol will be skipped.'
|
||||
}
|
||||
description={t('csv.insertCsvColumns.commentCharacterDescription')}
|
||||
/>
|
||||
<SelectWithDesc
|
||||
selected={values.customFill}
|
||||
options={[
|
||||
{ label: 'Fill With Empty Values', value: false },
|
||||
{ label: 'Fill With Customs Values', value: true }
|
||||
{
|
||||
label: t('csv.insertCsvColumns.fillWithEmptyValues'),
|
||||
value: false
|
||||
},
|
||||
{
|
||||
label: t('csv.insertCsvColumns.fillWithCustomValues'),
|
||||
value: true
|
||||
}
|
||||
]}
|
||||
onChange={(value) => {
|
||||
updateField('customFill', value);
|
||||
|
|
@ -192,48 +196,59 @@ export default function InsertCsvColumns({
|
|||
updateField('customFillValue', ''); // Reset custom fill value
|
||||
}
|
||||
}}
|
||||
description={
|
||||
'If the input CSV file is incomplete (missing values), then add empty fields or custom symbols to records to make a well-formed CSV?'
|
||||
}
|
||||
description={t('csv.insertCsvColumns.customFillDescription')}
|
||||
/>
|
||||
{values.customFill && (
|
||||
<TextFieldWithDesc
|
||||
value={values.customFillValue}
|
||||
onOwnChange={(val) => updateField('customFillValue', val)}
|
||||
description={
|
||||
'Use this custom value to fill in missing fields. (Works only with "Custom Values" mode above.)'
|
||||
}
|
||||
description={t('csv.insertCsvColumns.customFillValueDescription')}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Position Options',
|
||||
title: t('csv.insertCsvColumns.positionOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<SelectWithDesc
|
||||
selected={values.insertingPosition}
|
||||
options={[
|
||||
{ label: 'Prepend columns', value: 'prepend' },
|
||||
{ label: 'Append columns', value: 'append' },
|
||||
{ label: 'Custom position', value: 'custom' }
|
||||
{
|
||||
label: t('csv.insertCsvColumns.prependColumns'),
|
||||
value: 'prepend'
|
||||
},
|
||||
{
|
||||
label: t('csv.insertCsvColumns.appendColumns'),
|
||||
value: 'append'
|
||||
},
|
||||
{
|
||||
label: t('csv.insertCsvColumns.customPosition'),
|
||||
value: 'custom'
|
||||
}
|
||||
]}
|
||||
onChange={(value) => updateField('insertingPosition', value)}
|
||||
description={'Specify where to insert the columns in the CSV file.'}
|
||||
description={t('csv.insertCsvColumns.insertingPositionDescription')}
|
||||
/>
|
||||
|
||||
{values.insertingPosition === 'custom' && (
|
||||
<SelectWithDesc
|
||||
selected={values.customPostionOptions}
|
||||
options={[
|
||||
{ label: 'Header name', value: 'headerName' },
|
||||
{ label: 'Position ', value: 'rowNumber' }
|
||||
{
|
||||
label: t('csv.insertCsvColumns.headerName'),
|
||||
value: 'headerName'
|
||||
},
|
||||
{
|
||||
label: t('csv.insertCsvColumns.position'),
|
||||
value: 'rowNumber'
|
||||
}
|
||||
]}
|
||||
onChange={(value) => updateField('customPostionOptions', value)}
|
||||
description={
|
||||
'Select the method to insert the columns in the CSV file.'
|
||||
}
|
||||
description={t(
|
||||
'csv.insertCsvColumns.customPositionOptionsDescription'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -243,42 +258,53 @@ export default function InsertCsvColumns({
|
|||
selected={values.headerName}
|
||||
options={headerOptions}
|
||||
onChange={(value) => updateField('headerName', value)}
|
||||
description={
|
||||
'Header of the column you want to insert columns after.'
|
||||
}
|
||||
description={t('csv.insertCsvColumns.headerNameDescription')}
|
||||
/>
|
||||
)}
|
||||
|
||||
{values.insertingPosition === 'custom' &&
|
||||
values.customPostionOptions === 'rowNumber' && (
|
||||
<TextFieldWithDesc
|
||||
value={values.rowNumber}
|
||||
onOwnChange={(val) => updateField('rowNumber', Number(val))}
|
||||
inputProps={{ min: 1, max: headers.length }}
|
||||
type="number"
|
||||
description={
|
||||
'Number of the column you want to insert columns after.'
|
||||
value={values.rowNumber.toString()}
|
||||
onOwnChange={(val) =>
|
||||
updateField('rowNumber', parseInt(val) || 0)
|
||||
}
|
||||
description={t('csv.insertCsvColumns.rowNumberDescription')}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolTextInput value={input} title="Input CSV" onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('csv.insertCsvColumns.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult
|
||||
title={t('csv.insertCsvColumns.resultTitle')}
|
||||
value={result}
|
||||
extension={'csv'}
|
||||
/>
|
||||
}
|
||||
resultComponent={<ToolTextResult title="Output CSV" value={result} />}
|
||||
initialValues={initialValues}
|
||||
exampleCards={exampleCards}
|
||||
getGroups={getGroups}
|
||||
setInput={setInput}
|
||||
compute={compute}
|
||||
toolInfo={{ title: `What is a ${title}?`, description: longDescription }}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
toolInfo={{
|
||||
title: t('csv.insertCsvColumns.toolInfo.title'),
|
||||
description: t('csv.insertCsvColumns.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import SimpleRadio from '@components/options/SimpleRadio';
|
|||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import { processImage } from './service';
|
||||
import { InitialValuesType } from './types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
resizeMethod: 'pixels' as 'pixels' | 'percentage',
|
||||
|
|
@ -45,6 +46,7 @@ const validationSchema = Yup.object({
|
|||
});
|
||||
|
||||
export default function ResizeImage({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
|
||||
|
|
@ -58,22 +60,20 @@ export default function ResizeImage({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Resize Method',
|
||||
title: t('image.resize.resizeMethod'),
|
||||
component: (
|
||||
<Box>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('resizeMethod', 'pixels')}
|
||||
checked={values.resizeMethod === 'pixels'}
|
||||
description={'Resize by specifying dimensions in pixels.'}
|
||||
title={'Resize by Pixels'}
|
||||
description={t('image.resize.resizeByPixelsDescription')}
|
||||
title={t('image.resize.resizeByPixels')}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('resizeMethod', 'percentage')}
|
||||
checked={values.resizeMethod === 'percentage'}
|
||||
description={
|
||||
'Resize by specifying a percentage of the original size.'
|
||||
}
|
||||
title={'Resize by Percentage'}
|
||||
description={t('image.resize.resizeByPercentageDescription')}
|
||||
title={t('image.resize.resizeByPercentage')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -81,7 +81,7 @@ export default function ResizeImage({ title }: ToolComponentProps) {
|
|||
...(values.resizeMethod === 'pixels'
|
||||
? [
|
||||
{
|
||||
title: 'Dimension Type',
|
||||
title: t('image.resize.dimensionType'),
|
||||
component: (
|
||||
<Box>
|
||||
<CheckboxWithDesc
|
||||
|
|
@ -89,35 +89,29 @@ export default function ResizeImage({ title }: ToolComponentProps) {
|
|||
onChange={(value) =>
|
||||
updateField('maintainAspectRatio', value)
|
||||
}
|
||||
description={
|
||||
'Maintain the original aspect ratio of the image.'
|
||||
}
|
||||
title={'Maintain Aspect Ratio'}
|
||||
description={t('image.resize.maintainAspectRatioDescription')}
|
||||
title={t('image.resize.maintainAspectRatio')}
|
||||
/>
|
||||
{values.maintainAspectRatio && (
|
||||
<Box>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('dimensionType', 'width')}
|
||||
checked={values.dimensionType === 'width'}
|
||||
description={
|
||||
'Specify the width in pixels and calculate height based on aspect ratio.'
|
||||
}
|
||||
title={'Set Width'}
|
||||
description={t('image.resize.setWidthDescription')}
|
||||
title={t('image.resize.setWidth')}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('dimensionType', 'height')}
|
||||
checked={values.dimensionType === 'height'}
|
||||
description={
|
||||
'Specify the height in pixels and calculate width based on aspect ratio.'
|
||||
}
|
||||
title={'Set Height'}
|
||||
description={t('image.resize.setHeightDescription')}
|
||||
title={t('image.resize.setHeight')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<TextFieldWithDesc
|
||||
value={values.width}
|
||||
onOwnChange={(val) => updateField('width', val)}
|
||||
description={'Width (in pixels)'}
|
||||
description={t('image.resize.widthDescription')}
|
||||
disabled={
|
||||
values.maintainAspectRatio &&
|
||||
values.dimensionType === 'height'
|
||||
|
|
@ -131,7 +125,7 @@ export default function ResizeImage({ title }: ToolComponentProps) {
|
|||
<TextFieldWithDesc
|
||||
value={values.height}
|
||||
onOwnChange={(val) => updateField('height', val)}
|
||||
description={'Height (in pixels)'}
|
||||
description={t('image.resize.heightDescription')}
|
||||
disabled={
|
||||
values.maintainAspectRatio &&
|
||||
values.dimensionType === 'width'
|
||||
|
|
@ -148,15 +142,13 @@ export default function ResizeImage({ title }: ToolComponentProps) {
|
|||
]
|
||||
: [
|
||||
{
|
||||
title: 'Percentage',
|
||||
title: t('image.resize.percentage'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.percentage}
|
||||
onOwnChange={(val) => updateField('percentage', val)}
|
||||
description={
|
||||
'Percentage of original size (e.g., 50 for half size, 200 for double size)'
|
||||
}
|
||||
description={t('image.resize.percentageDescription')}
|
||||
inputProps={{
|
||||
'data-testid': 'percentage-input',
|
||||
type: 'number',
|
||||
|
|
@ -183,20 +175,19 @@ export default function ResizeImage({ title }: ToolComponentProps) {
|
|||
value={input}
|
||||
onChange={setInput}
|
||||
accept={['image/jpeg', 'image/png', 'image/svg+xml', 'image/gif']}
|
||||
title={'Input Image'}
|
||||
title={t('image.resize.inputTitle')}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolFileResult
|
||||
title={'Resized Image'}
|
||||
title={t('image.resize.resultTitle')}
|
||||
value={result}
|
||||
extension={input?.name.split('.').pop() || 'png'}
|
||||
/>
|
||||
}
|
||||
toolInfo={{
|
||||
title: 'Resize Image',
|
||||
description:
|
||||
'This tool allows you to resize JPG, PNG, SVG, or GIF images. You can resize by specifying dimensions in pixels or by percentage, with options to maintain the original aspect ratio.'
|
||||
title: t('image.resize.toolInfo.title'),
|
||||
description: t('image.resize.toolInfo.description')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
40
src/pages/tools/image/i18n/en.json
Normal file
40
src/pages/tools/image/i18n/en.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"resize": {
|
||||
"title": "Resize Image",
|
||||
"description": "Resize images to different dimensions.",
|
||||
"inputTitle": "Input Image",
|
||||
"resultTitle": "Resized Image",
|
||||
"resizeMethod": "Resize Method",
|
||||
"resizeByPixels": "Resize by Pixels",
|
||||
"resizeByPixelsDescription": "Resize by specifying dimensions in pixels.",
|
||||
"resizeByPercentage": "Resize by Percentage",
|
||||
"resizeByPercentageDescription": "Resize by specifying a percentage of the original size.",
|
||||
"dimensionType": "Dimension Type",
|
||||
"maintainAspectRatio": "Maintain Aspect Ratio",
|
||||
"maintainAspectRatioDescription": "Maintain the original aspect ratio of the image.",
|
||||
"setWidth": "Set Width",
|
||||
"setWidthDescription": "Specify the width in pixels and calculate height based on aspect ratio.",
|
||||
"setHeight": "Set Height",
|
||||
"setHeightDescription": "Specify the height in pixels and calculate width based on aspect ratio.",
|
||||
"widthDescription": "Width (in pixels)",
|
||||
"heightDescription": "Height (in pixels)",
|
||||
"percentage": "Percentage",
|
||||
"percentageDescription": "Percentage of original size (e.g., 50 for half size, 200 for double size)",
|
||||
"toolInfo": {
|
||||
"title": "Resize Image",
|
||||
"description": "This tool allows you to resize JPG, PNG, SVG, or GIF images. You can resize by specifying dimensions in pixels or by percentage, with options to maintain the original aspect ratio."
|
||||
}
|
||||
},
|
||||
"compress": {
|
||||
"title": "Compress Image",
|
||||
"description": "Reduce image file size while maintaining quality.",
|
||||
"inputTitle": "Input image",
|
||||
"resultTitle": "Compressed image"
|
||||
},
|
||||
"crop": {
|
||||
"title": "Crop Image",
|
||||
"description": "Crop images to remove unwanted areas.",
|
||||
"inputTitle": "Input image",
|
||||
"resultTitle": "Cropped image"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('json', {
|
||||
name: 'Escape JSON',
|
||||
path: 'escape-json',
|
||||
icon: 'lets-icons:json-light',
|
||||
icon: 'material-symbols:code',
|
||||
description:
|
||||
'Free online JSON escaper. Just load your JSON in the input field and it will automatically get escaped. In the tool options, you can optionally enable wrapping the escaped JSON in double quotes to get an escaped JSON string.',
|
||||
shortDescription: 'Quickly escape special JSON characters.',
|
||||
longDescription: `This tool converts special characters in JSON files and data structures into their escaped versions. Such special characters are, for example, double quotes, newline characters, backslashes, tabs, and many others. If these characters aren't escaped and appear in a raw JSON string without escaping, they can lead to errors in data parsing. The program turns them into safe versions by adding a backslash (\\) before the character, changing its interpretation. Additionally, you can enable the "Wrap Output in Quotes" checkbox in the options, which adds double quotes around the resulting escaped JSON data. This is useful when the escaped JSON data needs to be used as a string in other data structures or the JavaScript programming language. Json-abulous!`,
|
||||
keywords: ['escape', 'json'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Escape special characters in JSON strings. Convert JSON data to properly escaped format for safe transmission or storage.',
|
||||
shortDescription: 'Escape special characters in JSON',
|
||||
keywords: ['json', 'escape', 'characters', 'format'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'json.escapeJson.name',
|
||||
description: 'json.escapeJson.description',
|
||||
shortDescription: 'json.escapeJson.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
39
src/pages/tools/json/i18n/en.json
Normal file
39
src/pages/tools/json/i18n/en.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"prettify": {
|
||||
"title": "Prettify JSON",
|
||||
"description": "Format JSON with proper indentation and spacing.",
|
||||
"inputTitle": "Input JSON",
|
||||
"resultTitle": "Prettified JSON",
|
||||
"indentation": "Indentation",
|
||||
"useSpaces": "Use Spaces",
|
||||
"useSpacesDescription": "Indent output with spaces",
|
||||
"useTabs": "Use Tabs",
|
||||
"useTabsDescription": "Indent output with tabs.",
|
||||
"toolInfo": {
|
||||
"title": "Prettify JSON",
|
||||
"description": "This tool allows you to format JSON data with proper indentation and spacing, making it more readable and easier to work with."
|
||||
}
|
||||
},
|
||||
"minify": {
|
||||
"title": "Minify JSON",
|
||||
"description": "Remove all unnecessary whitespace from JSON.",
|
||||
"inputTitle": "Input JSON",
|
||||
"resultTitle": "Minified JSON",
|
||||
"toolInfo": {
|
||||
"title": "What Is JSON Minification?",
|
||||
"description": "JSON minification is the process of removing all unnecessary whitespace characters from JSON data while maintaining its validity. This includes removing spaces, newlines, and indentation that aren't required for the JSON to be parsed correctly. Minification reduces the size of JSON data, making it more efficient for storage and transmission while keeping the exact same data structure and values."
|
||||
}
|
||||
},
|
||||
"validateJson": {
|
||||
"title": "Validate JSON",
|
||||
"description": "Check if JSON is valid and well-formed.",
|
||||
"inputTitle": "Input JSON",
|
||||
"resultTitle": "Validation Result",
|
||||
"validJson": "✅ Valid JSON",
|
||||
"invalidJson": "❌ {{error}}",
|
||||
"toolInfo": {
|
||||
"title": "What is JSON Validation?",
|
||||
"description": "JSON (JavaScript Object Notation) is a lightweight data-interchange format. JSON validation ensures that the structure of the data conforms to the JSON standard. A valid JSON object must have: - Property names enclosed in double quotes. - Properly balanced curly braces {}. - No trailing commas after the last key-value pair. - Proper nesting of objects and arrays. This tool checks the input JSON and provides feedback to help identify and fix common errors."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,17 @@ import { defineTool } from '@tools/defineTool';
|
|||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('json', {
|
||||
name: 'Convert JSON to XML',
|
||||
name: 'JSON to XML',
|
||||
path: 'json-to-xml',
|
||||
icon: 'mdi-light:xml',
|
||||
description: 'Convert JSON files to XML format with customizable options.',
|
||||
shortDescription: 'Convert JSON data to XML format',
|
||||
keywords: ['json', 'xml', 'convert', 'transform', 'parse'],
|
||||
component: lazy(() => import('./index'))
|
||||
icon: 'material-symbols:code',
|
||||
description:
|
||||
'Convert JSON data to XML format. Transform structured JSON objects into well-formed XML documents.',
|
||||
shortDescription: 'Convert JSON to XML format',
|
||||
keywords: ['json', 'xml', 'convert', 'transform'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'json.jsonToXml.name',
|
||||
description: 'json.jsonToXml.description',
|
||||
shortDescription: 'json.jsonToXml.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import ToolTextResult from '@components/result/ToolTextResult';
|
|||
import { minifyJson } from './service';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type InitialValuesType = Record<string, never>;
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function MinifyJson({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -58,11 +60,15 @@ export default function MinifyJson({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input JSON" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('json.minify.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult
|
||||
title="Minified JSON"
|
||||
title={t('json.minify.resultTitle')}
|
||||
value={result}
|
||||
extension={'json'}
|
||||
/>
|
||||
|
|
@ -70,9 +76,8 @@ export default function MinifyJson({ title }: ToolComponentProps) {
|
|||
initialValues={initialValues}
|
||||
getGroups={null}
|
||||
toolInfo={{
|
||||
title: 'What Is JSON Minification?',
|
||||
description:
|
||||
"JSON minification is the process of removing all unnecessary whitespace characters from JSON data while maintaining its validity. This includes removing spaces, newlines, and indentation that aren't required for the JSON to be parsed correctly. Minification reduces the size of JSON data, making it more efficient for storage and transmission while keeping the exact same data structure and values."
|
||||
title: t('json.minify.toolInfo.title'),
|
||||
description: t('json.minify.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('json', {
|
||||
name: 'Minify JSON',
|
||||
path: 'minify',
|
||||
icon: 'lets-icons:json-light',
|
||||
icon: 'material-symbols:code',
|
||||
description:
|
||||
'Minify your JSON by removing all unnecessary whitespace and formatting. This tool compresses JSON data to its smallest possible size while maintaining valid JSON structure.',
|
||||
shortDescription: 'Quickly compress JSON file.',
|
||||
keywords: ['minify', 'compress', 'minimize', 'json', 'compact'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Minify JSON data by removing unnecessary whitespace and formatting. Reduce file size while maintaining data integrity.',
|
||||
shortDescription: 'Minify JSON by removing whitespace',
|
||||
keywords: ['json', 'minify', 'compress', 'whitespace'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'json.minify.name',
|
||||
description: 'json.minify.description',
|
||||
shortDescription: 'json.minify.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import RadioWithTextField from '@components/options/RadioWithTextField';
|
|||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import { isNumber, updateNumberField } from '../../../../utils/string';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type InitialValuesType = {
|
||||
indentationType: 'tab' | 'space';
|
||||
|
|
@ -115,6 +116,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function PrettifyJson({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -128,11 +130,15 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
|
|||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input JSON'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('json.prettify.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult
|
||||
title={'Pretty JSON'}
|
||||
title={t('json.prettify.resultTitle')}
|
||||
value={result}
|
||||
extension={'json'}
|
||||
/>
|
||||
|
|
@ -140,14 +146,14 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
|
|||
initialValues={initialValues}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Indentation',
|
||||
title: t('json.prettify.indentation'),
|
||||
component: (
|
||||
<Box>
|
||||
<RadioWithTextField
|
||||
checked={values.indentationType === 'space'}
|
||||
title={'Use Spaces'}
|
||||
title={t('json.prettify.useSpaces')}
|
||||
fieldName={'indentationType'}
|
||||
description={'Indent output with spaces'}
|
||||
description={t('json.prettify.useSpacesDescription')}
|
||||
value={values.spacesCount.toString()}
|
||||
onRadioClick={() => updateField('indentationType', 'space')}
|
||||
onTextChange={(val) =>
|
||||
|
|
@ -157,8 +163,8 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
|
|||
<SimpleRadio
|
||||
onClick={() => updateField('indentationType', 'tab')}
|
||||
checked={values.indentationType === 'tab'}
|
||||
description={'Indent output with tabs.'}
|
||||
title={'Use Tabs'}
|
||||
description={t('json.prettify.useTabsDescription')}
|
||||
title={t('json.prettify.useTabs')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -168,9 +174,8 @@ export default function PrettifyJson({ title }: ToolComponentProps) {
|
|||
setInput={setInput}
|
||||
exampleCards={exampleCards}
|
||||
toolInfo={{
|
||||
title: 'What Is a JSON Prettifier?',
|
||||
description:
|
||||
'This tool adds consistent formatting to the data in JavaScript Object Notation (JSON) format. This transformation makes the JSON code more readable, making it easier to understand and edit. The program parses the JSON data structure into tokens and then reformats them by adding indentation and line breaks. If the data is hierarchial, then it adds indentation at the beginning of lines to visually show the depth of the JSON and adds newlines to break long single-line JSON arrays into multiple shorter, more readable ones. Additionally, this utility can remove unnecessary spaces and tabs from your JSON code (especially leading and trailing whitespaces), making it more compact. You can choose the line indentation method in the options: indent with spaces or indent with tabs. When using spaces, you can also specify how many spaces to use for each indentation level (usually 2 or 4 spaces). '
|
||||
title: t('json.prettify.toolInfo.title'),
|
||||
description: t('json.prettify.toolInfo.description')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('json', {
|
||||
name: 'Prettify JSON',
|
||||
path: 'prettify',
|
||||
icon: 'lets-icons:json-light',
|
||||
icon: 'material-symbols:code',
|
||||
description:
|
||||
"Just load your JSON in the input field and it will automatically get prettified. In the tool options, you can choose whether to use spaces or tabs for indentation and if you're using spaces, you can specify the number of spaces to add per indentation level.",
|
||||
shortDescription: 'Quickly beautify a JSON data structure.',
|
||||
keywords: ['prettify'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Format and beautify JSON data with proper indentation and spacing. Make JSON files more readable and organized.',
|
||||
shortDescription: 'Format and beautify JSON code',
|
||||
keywords: ['json', 'prettify', 'format', 'beautify'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'json.prettify.name',
|
||||
description: 'json.prettify.description',
|
||||
shortDescription: 'json.prettify.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,18 +4,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('json', {
|
||||
name: 'Stringify JSON',
|
||||
path: 'stringify',
|
||||
icon: 'ant-design:field-string-outlined',
|
||||
icon: 'material-symbols:code',
|
||||
description:
|
||||
'Convert JavaScript objects and arrays into their JSON string representation. Options include custom indentation and HTML character escaping for web-safe JSON strings.',
|
||||
shortDescription: 'Convert JavaScript objects to JSON strings',
|
||||
keywords: [
|
||||
'stringify',
|
||||
'serialize',
|
||||
'convert',
|
||||
'object',
|
||||
'array',
|
||||
'json',
|
||||
'string'
|
||||
],
|
||||
component: lazy(() => import('./index'))
|
||||
'Convert JavaScript objects to JSON string format. Serialize data structures into JSON strings for storage or transmission.',
|
||||
shortDescription: 'Convert objects to JSON string',
|
||||
keywords: ['json', 'stringify', 'serialize', 'convert'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'json.stringify.name',
|
||||
description: 'json.stringify.description',
|
||||
shortDescription: 'json.stringify.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@ import { defineTool } from '@tools/defineTool';
|
|||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('json', {
|
||||
name: 'Convert TSV to JSON',
|
||||
name: 'TSV to JSON',
|
||||
path: 'tsv-to-json',
|
||||
icon: 'material-symbols:tsv-rounded',
|
||||
icon: 'material-symbols:code',
|
||||
description:
|
||||
'Convert TSV files to JSON format with customizable options for delimiters, quotes, and output formatting. Support for headers, comments, and dynamic type conversion.',
|
||||
shortDescription: 'Convert TSV data to JSON format.',
|
||||
longDescription:
|
||||
'This tool allows you to convert TSV (Tab-Separated Values) files into JSON format. You can customize the conversion process by specifying delimiters, quote characters, and whether to use headers. It also supports dynamic type conversion for values, handling comments, and skipping empty lines. The output can be formatted with indentation or minified as needed.',
|
||||
keywords: ['tsv', 'json', 'convert', 'transform', 'parse'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Convert TSV (Tab-Separated Values) data to JSON format. Transform tabular data into structured JSON objects.',
|
||||
shortDescription: 'Convert TSV to JSON format',
|
||||
keywords: ['tsv', 'json', 'convert', 'tabular'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'json.tsvToJson.name',
|
||||
description: 'json.tsvToJson.description',
|
||||
shortDescription: 'json.tsvToJson.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { CardExampleType } from '@components/examples/ToolExamples';
|
|||
import { validateJson } from './service';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const exampleCards: CardExampleType<{}>[] = [
|
||||
{
|
||||
|
|
@ -46,6 +47,7 @@ const exampleCards: CardExampleType<{}>[] = [
|
|||
];
|
||||
|
||||
export default function ValidateJson({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -53,9 +55,9 @@ export default function ValidateJson({ title }: ToolComponentProps) {
|
|||
const { valid, error } = validateJson(input);
|
||||
|
||||
if (valid) {
|
||||
setResult('✅ Valid JSON');
|
||||
setResult(t('json.validateJson.validJson'));
|
||||
} else {
|
||||
setResult(`❌ ${error}`);
|
||||
setResult(t('json.validateJson.invalidJson', { error }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -63,25 +65,23 @@ export default function ValidateJson({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input JSON" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('json.validateJson.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title="Validation Result" value={result} />
|
||||
<ToolTextResult
|
||||
title={t('json.validateJson.resultTitle')}
|
||||
value={result}
|
||||
/>
|
||||
}
|
||||
initialValues={{}}
|
||||
getGroups={null}
|
||||
toolInfo={{
|
||||
title: 'What is JSON Validation?',
|
||||
description: `
|
||||
JSON (JavaScript Object Notation) is a lightweight data-interchange format.
|
||||
JSON validation ensures that the structure of the data conforms to the JSON standard.
|
||||
A valid JSON object must have:
|
||||
- Property names enclosed in double quotes.
|
||||
- Properly balanced curly braces {}.
|
||||
- No trailing commas after the last key-value pair.
|
||||
- Proper nesting of objects and arrays.
|
||||
This tool checks the input JSON and provides feedback to help identify and fix common errors.
|
||||
`
|
||||
title: t('json.validateJson.toolInfo.title'),
|
||||
description: t('json.validateJson.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,16 @@ import { lazy } from 'react';
|
|||
|
||||
export const tool = defineTool('json', {
|
||||
name: 'Validate JSON',
|
||||
path: 'validateJson',
|
||||
icon: 'lets-icons:json-light',
|
||||
path: 'validate-json',
|
||||
icon: 'material-symbols:check-circle',
|
||||
description:
|
||||
'Validate JSON data and identify formatting issues such as missing quotes, trailing commas, and incorrect brackets.',
|
||||
shortDescription: 'Quickly validate a JSON data structure.',
|
||||
keywords: ['validate', 'json', 'syntax'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Validate JSON code for syntax errors and proper structure. Check if JSON documents follow correct formatting rules.',
|
||||
shortDescription: 'Validate JSON code for errors',
|
||||
keywords: ['json', 'validate', 'check', 'syntax', 'errors'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'json.validateJson.name',
|
||||
description: 'json.validateJson.description',
|
||||
shortDescription: 'json.validateJson.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
|||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import * as Yup from 'yup';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface InitialValuesType {
|
||||
splitOperatorType: SplitOperatorType;
|
||||
|
|
@ -101,6 +102,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function Duplicate({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -134,55 +136,53 @@ export default function Duplicate({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Split Options',
|
||||
title: t('list.duplicate.splitOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('splitOperatorType', 'symbol')}
|
||||
checked={values.splitOperatorType === 'symbol'}
|
||||
title={'Split by Symbol'}
|
||||
title={t('list.duplicate.splitBySymbol')}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('splitOperatorType', 'regex')}
|
||||
checked={values.splitOperatorType === 'regex'}
|
||||
title={'Split by Regular Expression'}
|
||||
title={t('list.duplicate.splitByRegex')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.splitSeparator}
|
||||
onOwnChange={(val) => updateField('splitSeparator', val)}
|
||||
description={'Separator to split the list'}
|
||||
description={t('list.duplicate.splitSeparatorDescription')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.joinSeparator}
|
||||
onOwnChange={(val) => updateField('joinSeparator', val)}
|
||||
description={'Separator to join the duplicated list'}
|
||||
description={t('list.duplicate.joinSeparatorDescription')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Duplication Options',
|
||||
title: t('list.duplicate.duplicationOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.copy}
|
||||
onOwnChange={(val) => updateField('copy', val)}
|
||||
description={'Number of copies (can be fractional)'}
|
||||
description={t('list.duplicate.copyDescription')}
|
||||
type="number"
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Concatenate'}
|
||||
title={t('list.duplicate.concatenate')}
|
||||
checked={values.concatenate}
|
||||
onChange={(checked) => updateField('concatenate', checked)}
|
||||
description={
|
||||
'Concatenate copies (if unchecked, items will be interweaved)'
|
||||
}
|
||||
description={t('list.duplicate.concatenateDescription')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Reverse'}
|
||||
title={t('list.duplicate.reverse')}
|
||||
checked={values.reverse}
|
||||
onChange={(checked) => updateField('reverse', checked)}
|
||||
description={'Reverse the duplicated items'}
|
||||
description={t('list.duplicate.reverseDescription')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -193,18 +193,24 @@ export default function Duplicate({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input List" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('list.duplicate.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title="Duplicated List" value={result} />
|
||||
<ToolTextResult
|
||||
title={t('list.duplicate.resultTitle')}
|
||||
value={result}
|
||||
/>
|
||||
}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
validationSchema={validationSchema}
|
||||
toolInfo={{
|
||||
title: 'List Duplication',
|
||||
description:
|
||||
"This tool allows you to duplicate items in a list. You can specify the number of copies (including fractional values), control whether items are concatenated or interweaved, and even reverse the duplicated items. It's useful for creating repeated patterns, generating test data, or expanding lists with predictable content."
|
||||
title: t('list.duplicate.toolInfo.title'),
|
||||
description: t('list.duplicate.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('list', {
|
||||
name: 'Duplicate',
|
||||
path: 'duplicate',
|
||||
icon: 'mdi:content-duplicate',
|
||||
icon: 'material-symbols-light:content-copy',
|
||||
description:
|
||||
'A tool to duplicate each item in a list a specified number of times. Perfect for creating repeated patterns, test data, or expanding datasets.',
|
||||
shortDescription: 'Repeat items in a list multiple times.',
|
||||
"World's simplest browser-based utility for duplicating list items. Input your list and specify duplication criteria to create copies of items. Perfect for data expansion, testing, or creating repeated patterns.",
|
||||
shortDescription: 'Duplicate list items with specified criteria',
|
||||
keywords: ['duplicate'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.duplicate.name',
|
||||
description: 'list.duplicate.description',
|
||||
shortDescription: 'list.duplicate.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('list', {
|
||||
name: 'Find most popular',
|
||||
path: 'find-most-popular',
|
||||
icon: 'material-symbols-light:query-stats',
|
||||
icon: 'material-symbols-light:trending-up',
|
||||
description:
|
||||
'A tool to identify and count the most frequently occurring items in a list. Useful for data analysis, finding trends, or identifying common elements.',
|
||||
shortDescription: 'Find most common items in a list.',
|
||||
"World's simplest browser-based utility for finding the most popular items in a list. Input your list and instantly get the items that appear most frequently. Perfect for data analysis, trend identification, or finding common elements.",
|
||||
shortDescription: 'Find most frequently occurring items',
|
||||
keywords: ['find', 'most', 'popular'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.findMostPopular.name',
|
||||
description: 'list.findMostPopular.description',
|
||||
shortDescription: 'list.findMostPopular.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { findUniqueCompute, SplitOperatorType } from './service';
|
|||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
splitOperatorType: 'symbol' as SplitOperatorType,
|
||||
|
|
@ -35,6 +36,7 @@ const splitOperators: {
|
|||
];
|
||||
|
||||
export default function FindUnique() {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||
|
|
@ -64,18 +66,27 @@ export default function FindUnique() {
|
|||
|
||||
return (
|
||||
<ToolContent
|
||||
title="Find Unique"
|
||||
title={t('list.findUnique.title')}
|
||||
initialValues={initialValues}
|
||||
compute={compute}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input list'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('list.findUnique.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult
|
||||
title={t('list.findUnique.resultTitle')}
|
||||
value={result}
|
||||
/>
|
||||
}
|
||||
resultComponent={<ToolTextResult title={'Unique items'} value={result} />}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Input List Delimiter',
|
||||
title: t('list.findUnique.inputListDelimiter'),
|
||||
component: (
|
||||
<Box>
|
||||
{splitOperators.map(({ title, description, type }) => (
|
||||
|
|
@ -88,7 +99,7 @@ export default function FindUnique() {
|
|||
/>
|
||||
))}
|
||||
<TextFieldWithDesc
|
||||
description={'Set a delimiting symbol or regular expression.'}
|
||||
description={t('list.findUnique.delimiterDescription')}
|
||||
value={values.splitSeparator}
|
||||
onOwnChange={(val) => updateField('splitSeparator', val)}
|
||||
/>
|
||||
|
|
@ -96,7 +107,7 @@ export default function FindUnique() {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Output List Delimiter',
|
||||
title: t('list.findUnique.outputListDelimiter'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
|
|
@ -104,18 +115,14 @@ export default function FindUnique() {
|
|||
onOwnChange={(value) => updateField('joinSeparator', value)}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Trim top list items'}
|
||||
description={
|
||||
'Remove leading and trailing spaces before comparing items'
|
||||
}
|
||||
title={t('list.findUnique.trimItems')}
|
||||
description={t('list.findUnique.trimItemsDescription')}
|
||||
checked={values.trimItems}
|
||||
onChange={(value) => updateField('trimItems', value)}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Skip empty items'}
|
||||
description={
|
||||
"Don't include the empty list items in the output."
|
||||
}
|
||||
title={t('list.findUnique.skipEmptyItems')}
|
||||
description={t('list.findUnique.skipEmptyItemsDescription')}
|
||||
checked={values.deleteEmptyItems}
|
||||
onChange={(value) => updateField('deleteEmptyItems', value)}
|
||||
/>
|
||||
|
|
@ -123,22 +130,20 @@ export default function FindUnique() {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Unique Item Options',
|
||||
title: t('list.findUnique.uniqueItemOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<CheckboxWithDesc
|
||||
title={'Find Absolutely Unique Items'}
|
||||
description={
|
||||
'Display only those items of the list that exist in a single copy.'
|
||||
}
|
||||
title={t('list.findUnique.findAbsolutelyUniqueItems')}
|
||||
description={t(
|
||||
'list.findUnique.findAbsolutelyUniqueItemsDescription'
|
||||
)}
|
||||
checked={values.absolutelyUnique}
|
||||
onChange={(value) => updateField('absolutelyUnique', value)}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Case Sensitive Items'}
|
||||
description={
|
||||
'Output items with different case as unique elements in the list.'
|
||||
}
|
||||
title={t('list.findUnique.caseSensitiveItems')}
|
||||
description={t('list.findUnique.caseSensitiveItemsDescription')}
|
||||
checked={values.caseSensitive}
|
||||
onChange={(value) => updateField('caseSensitive', value)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('list', {
|
||||
name: 'Find unique',
|
||||
path: 'find-unique',
|
||||
icon: 'mynaui:one',
|
||||
description: "World's simplest browser-based utility for finding unique items in a list. Just input your list with any separator, and it will automatically identify and extract unique items. Perfect for removing duplicates, finding distinct values, or analyzing data uniqueness. You can customize the input/output separators and choose whether to preserve the original order.",
|
||||
icon: 'material-symbols-light:search',
|
||||
description:
|
||||
"World's simplest browser-based utility for finding unique items in a list. Input your list and instantly get all unique values with duplicates removed. Perfect for data cleaning, deduplication, or finding distinct elements.",
|
||||
shortDescription: 'Find unique items in a list',
|
||||
keywords: ['find', 'unique'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.findUnique.name',
|
||||
description: 'list.findUnique.description',
|
||||
shortDescription: 'list.findUnique.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
|||
import { formatNumber } from '../../../../utils/number';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
splitOperatorType: 'symbol' as SplitOperatorType,
|
||||
|
|
@ -40,6 +41,7 @@ const splitOperators: {
|
|||
];
|
||||
|
||||
export default function FindUnique({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||
|
|
@ -78,28 +80,34 @@ export default function FindUnique({ title }: ToolComponentProps) {
|
|||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input list'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('list.group.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={'Grouped items'} value={result} />
|
||||
<ToolTextResult title={t('list.group.resultTitle')} value={result} />
|
||||
}
|
||||
initialValues={initialValues}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Input Item Separator',
|
||||
title: t('list.group.inputItemSeparator'),
|
||||
component: (
|
||||
<Box>
|
||||
{splitOperators.map(({ title, description, type }) => (
|
||||
<SimpleRadio
|
||||
key={type}
|
||||
onClick={() => updateField('splitOperatorType', type)}
|
||||
title={title}
|
||||
description={description}
|
||||
title={t(`list.group.splitOperators.${type}.title`)}
|
||||
description={t(
|
||||
`list.group.splitOperators.${type}.description`
|
||||
)}
|
||||
checked={values.splitOperatorType === type}
|
||||
/>
|
||||
))}
|
||||
<TextFieldWithDesc
|
||||
description={'Set a delimiting symbol or regular expression.'}
|
||||
description={t('list.group.splitSeparatorDescription')}
|
||||
value={values.splitSeparator}
|
||||
onOwnChange={(val) => updateField('splitSeparator', val)}
|
||||
/>
|
||||
|
|
@ -107,12 +115,12 @@ export default function FindUnique({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Group Size and Separators',
|
||||
title: t('list.group.groupSizeAndSeparators'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.groupNumber}
|
||||
description={'Number of items in a group'}
|
||||
description={t('list.group.groupNumberDescription')}
|
||||
type={'number'}
|
||||
onOwnChange={(value) =>
|
||||
updateField('groupNumber', formatNumber(value, 1))
|
||||
|
|
@ -120,52 +128,46 @@ export default function FindUnique({ title }: ToolComponentProps) {
|
|||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.itemSeparator}
|
||||
description={'Item separator character'}
|
||||
description={t('list.group.itemSeparatorDescription')}
|
||||
onOwnChange={(value) => updateField('itemSeparator', value)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.groupSeparator}
|
||||
description={'Group separator character'}
|
||||
description={t('list.group.groupSeparatorDescription')}
|
||||
onOwnChange={(value) => updateField('groupSeparator', value)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.leftWrap}
|
||||
description={"Group's left wrap symbol."}
|
||||
description={t('list.group.leftWrapDescription')}
|
||||
onOwnChange={(value) => updateField('leftWrap', value)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.rightWrap}
|
||||
description={"Group's right wrap symbol."}
|
||||
description={t('list.group.rightWrapDescription')}
|
||||
onOwnChange={(value) => updateField('rightWrap', value)}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Empty Items and Padding',
|
||||
title: t('list.group.emptyItemsAndPadding'),
|
||||
component: (
|
||||
<Box>
|
||||
<CheckboxWithDesc
|
||||
title={'Delete Empty Items'}
|
||||
description={
|
||||
"Ignore empty items and don't include them in the groups."
|
||||
}
|
||||
title={t('list.group.deleteEmptyItems')}
|
||||
description={t('list.group.deleteEmptyItemsDescription')}
|
||||
checked={values.deleteEmptyItems}
|
||||
onChange={(value) => updateField('deleteEmptyItems', value)}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Pad Non-full Groups'}
|
||||
description={
|
||||
'Fill non-full groups with a custom item (enter below).'
|
||||
}
|
||||
title={t('list.group.padNonFullGroups')}
|
||||
description={t('list.group.padNonFullGroupsDescription')}
|
||||
checked={values.padNonFullGroup}
|
||||
onChange={(value) => updateField('padNonFullGroup', value)}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.paddingChar}
|
||||
description={
|
||||
'Use this character or item to pad non-full groups.'
|
||||
}
|
||||
description={t('list.group.paddingCharDescription')}
|
||||
onOwnChange={(value) => updateField('paddingChar', value)}
|
||||
/>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,14 @@ export const tool = defineTool('list', {
|
|||
name: 'Group',
|
||||
path: 'group',
|
||||
icon: 'pajamas:group',
|
||||
description: "World's simplest browser-based utility for grouping list items. Input your list and specify grouping criteria to organize items into logical groups. Perfect for categorizing data, organizing information, or creating structured lists. Supports custom separators and various grouping options.",
|
||||
description:
|
||||
"World's simplest browser-based utility for grouping list items. Input your list and specify grouping criteria to organize items into logical groups. Perfect for categorizing data, organizing information, or creating structured lists. Supports custom separators and various grouping options.",
|
||||
shortDescription: 'Group list items by common properties',
|
||||
keywords: ['group'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.group.name',
|
||||
description: 'list.group.description',
|
||||
shortDescription: 'list.group.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
115
src/pages/tools/list/i18n/en.json
Normal file
115
src/pages/tools/list/i18n/en.json
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"group": {
|
||||
"title": "Group List",
|
||||
"description": "Group list items by common properties.",
|
||||
"inputTitle": "Input list",
|
||||
"resultTitle": "Grouped list",
|
||||
"groupingOptions": "Grouping Options",
|
||||
"groupByDescription": "Choose how to group the list items",
|
||||
"groupByValue": "Group by Value",
|
||||
"groupByValueDescription": "Group items that have the same value",
|
||||
"groupByLength": "Group by Length",
|
||||
"groupByLengthDescription": "Group items by their character length",
|
||||
"groupByFirstChar": "Group by First Character",
|
||||
"groupByFirstCharDescription": "Group items by their first character",
|
||||
"groupByLastChar": "Group by Last Character",
|
||||
"groupByLastCharDescription": "Group items by their last character",
|
||||
"toolInfo": {
|
||||
"title": "Group List",
|
||||
"description": "This tool allows you to group list items by various criteria such as value, length, or character position. It's useful for organizing and categorizing data."
|
||||
}
|
||||
},
|
||||
"reverse": {
|
||||
"title": "Reverse List",
|
||||
"description": "Reverse the order of items in a list.",
|
||||
"inputTitle": "Input list",
|
||||
"resultTitle": "Reversed list",
|
||||
"reverseOptions": "Reverse Options",
|
||||
"reverseEachLine": "Reverse Each Line",
|
||||
"reverseEachLineDescription": "Reverse each line separately instead of the entire list",
|
||||
"toolInfo": {
|
||||
"title": "Reverse List",
|
||||
"description": "This tool allows you to reverse the order of items in a list. You can reverse the entire list or each line separately."
|
||||
}
|
||||
},
|
||||
"sort": {
|
||||
"title": "Sort List",
|
||||
"description": "Sort list items in ascending or descending order.",
|
||||
"inputTitle": "Input list",
|
||||
"resultTitle": "Sorted list",
|
||||
"sortOptions": "Sort Options",
|
||||
"sortOrder": "Sort Order",
|
||||
"ascending": "Ascending",
|
||||
"descending": "Descending",
|
||||
"sortType": "Sort Type",
|
||||
"alphabetical": "Alphabetical",
|
||||
"numerical": "Numerical",
|
||||
"natural": "Natural",
|
||||
"toolInfo": {
|
||||
"title": "Sort List",
|
||||
"description": "This tool allows you to sort list items in various orders and types. You can sort alphabetically, numerically, or using natural sorting."
|
||||
}
|
||||
},
|
||||
"duplicate": {
|
||||
"title": "Find Duplicates",
|
||||
"description": "Find and remove duplicate items from a list.",
|
||||
"inputTitle": "Input list",
|
||||
"resultTitle": "Unique list",
|
||||
"duplicateOptions": "Duplicate Options",
|
||||
"removeDuplicates": "Remove Duplicates",
|
||||
"removeDuplicatesDescription": "Remove duplicate items from the list",
|
||||
"showDuplicates": "Show Duplicates",
|
||||
"showDuplicatesDescription": "Show only the duplicate items",
|
||||
"toolInfo": {
|
||||
"title": "Find Duplicates",
|
||||
"description": "This tool allows you to find and handle duplicate items in a list. You can remove duplicates or show only the duplicate items."
|
||||
}
|
||||
},
|
||||
"findUnique": {
|
||||
"title": "Find Unique",
|
||||
"description": "Find unique items in a list.",
|
||||
"inputTitle": "Input list",
|
||||
"resultTitle": "Unique items",
|
||||
"uniqueOptions": "Unique Options",
|
||||
"showUnique": "Show Unique",
|
||||
"showUniqueDescription": "Show only unique items",
|
||||
"showDuplicates": "Show Duplicates",
|
||||
"showDuplicatesDescription": "Show only duplicate items",
|
||||
"toolInfo": {
|
||||
"title": "Find Unique",
|
||||
"description": "This tool allows you to find unique items in a list. You can show unique items or duplicate items."
|
||||
}
|
||||
},
|
||||
"shuffle": {
|
||||
"title": "Shuffle List",
|
||||
"description": "Randomly shuffle the order of list items.",
|
||||
"inputTitle": "Input list",
|
||||
"resultTitle": "Shuffled list",
|
||||
"shuffleOptions": "Shuffle Options",
|
||||
"shuffleEachLine": "Shuffle Each Line",
|
||||
"shuffleEachLineDescription": "Shuffle each line separately instead of the entire list",
|
||||
"toolInfo": {
|
||||
"title": "Shuffle List",
|
||||
"description": "This tool allows you to randomly shuffle the order of items in a list. You can shuffle the entire list or each line separately."
|
||||
}
|
||||
},
|
||||
"wrap": {
|
||||
"title": "Wrap List",
|
||||
"description": "Add text before and after each list item.",
|
||||
"inputTitle": "Input List",
|
||||
"resultTitle": "Wrapped List",
|
||||
"splitOptions": "Split Options",
|
||||
"splitBySymbol": "Split by Symbol",
|
||||
"splitByRegex": "Split by Regular Expression",
|
||||
"splitSeparatorDescription": "Separator to split the list",
|
||||
"joinSeparatorDescription": "Separator to join the wrapped list",
|
||||
"removeEmptyItems": "Remove empty items",
|
||||
"wrapOptions": "Wrap Options",
|
||||
"leftTextDescription": "Text to add before each item",
|
||||
"rightTextDescription": "Text to add after each item",
|
||||
"toolInfo": {
|
||||
"title": "List Wrapping",
|
||||
"description": "This tool allows you to add text before and after each item in a list. You can specify different text for the left and right sides, and control how the list is processed. It's useful for adding quotes, brackets, or other formatting to list items, preparing data for different formats, or creating structured text."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
|||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
splitOperatorType: 'symbol' as SplitOperatorType,
|
||||
|
|
@ -111,6 +112,7 @@ argument`,
|
|||
];
|
||||
|
||||
export default function Reverse({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -119,15 +121,15 @@ export default function Reverse({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Splitter Mode',
|
||||
title: t('list.reverse.splitterMode'),
|
||||
component: (
|
||||
<Box>
|
||||
{splitOperators.map(({ title, description, type }) => (
|
||||
<SimpleRadio
|
||||
key={type}
|
||||
onClick={() => updateField('splitOperatorType', type)}
|
||||
title={title}
|
||||
description={description}
|
||||
title={t(`list.reverse.splitOperators.${type}.title`)}
|
||||
description={t(`list.reverse.splitOperators.${type}.description`)}
|
||||
checked={values.splitOperatorType === type}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -135,11 +137,11 @@ export default function Reverse({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Item Separator',
|
||||
title: t('list.reverse.itemSeparator'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description={'Set a delimiting symbol or regular expression.'}
|
||||
description={t('list.reverse.itemSeparatorDescription')}
|
||||
value={values.splitSeparator}
|
||||
onOwnChange={(val) => updateField('splitSeparator', val)}
|
||||
/>
|
||||
|
|
@ -147,11 +149,11 @@ export default function Reverse({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Output List Options',
|
||||
title: t('list.reverse.outputListOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description={'Output list item separator.'}
|
||||
description={t('list.reverse.outputSeparatorDescription')}
|
||||
value={values.joinSeparator}
|
||||
onOwnChange={(val) => updateField('joinSeparator', val)}
|
||||
/>
|
||||
|
|
@ -176,15 +178,18 @@ export default function Reverse({ title }: ToolComponentProps) {
|
|||
input={input}
|
||||
setInput={setInput}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input list'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('list.reverse.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={'Reversed list'} value={result} />
|
||||
<ToolTextResult title={t('list.reverse.resultTitle')} value={result} />
|
||||
}
|
||||
toolInfo={{
|
||||
title: 'What Is a List Reverser?',
|
||||
description:
|
||||
'With this utility, you can reverse the order of items in a list. The utility first splits the input list into individual items and then iterates through them from the last item to the first item, printing each item to the output during the iteration. The input list may contain anything that can be represented as textual data, which includes digits, numbers, strings, words, sentences, etc. The input item separator can also be a regular expression. For example, the regex /[;,]/ will allow you to use items that are either comma- or semicolon-separated. The input and output list items delimiters can be customized in the options. By default, both input and output lists are comma-separated. Listabulous!'
|
||||
title: t('list.reverse.toolInfo.title'),
|
||||
description: t('list.reverse.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,14 @@ export const tool = defineTool('list', {
|
|||
name: 'Reverse',
|
||||
path: 'reverse',
|
||||
icon: 'proicons:reverse',
|
||||
description: 'This is a super simple browser-based application prints all list items in reverse. The input items can be separated by any symbol and you can also change the separator of the reversed list items.',
|
||||
description:
|
||||
'This is a super simple browser-based application prints all list items in reverse. The input items can be separated by any symbol and you can also change the separator of the reversed list items.',
|
||||
shortDescription: 'Quickly reverse a list',
|
||||
keywords: ['reverse'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.reverse.name',
|
||||
description: 'list.reverse.description',
|
||||
shortDescription: 'list.reverse.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,8 +7,13 @@ export const tool = defineTool('list', {
|
|||
path: 'rotate',
|
||||
icon: 'material-symbols-light:rotate-right',
|
||||
description:
|
||||
'A tool to rotate items in a list by a specified number of positions. Shift elements left or right while maintaining their relative order.',
|
||||
shortDescription: 'Shift list items by position.',
|
||||
"World's simplest browser-based utility for rotating list items. Input your list and specify rotation amount to shift items by a specified number of positions. Perfect for data manipulation, circular shifts, or reordering lists.",
|
||||
shortDescription: 'Rotate list items by specified positions',
|
||||
keywords: ['rotate'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.rotate.name',
|
||||
description: 'list.rotate.description',
|
||||
shortDescription: 'list.rotate.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { shuffleList, SplitOperatorType } from './service';
|
|||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import { isNumber } from '@utils/string';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
splitOperatorType: 'symbol' as SplitOperatorType,
|
||||
|
|
@ -32,6 +33,7 @@ const splitOperators: {
|
|||
];
|
||||
|
||||
export default function Shuffle() {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||
|
|
@ -51,20 +53,24 @@ export default function Shuffle() {
|
|||
|
||||
return (
|
||||
<ToolContent
|
||||
title="Shuffle"
|
||||
title={t('list.shuffle.title')}
|
||||
initialValues={initialValues}
|
||||
compute={compute}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input list'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('list.shuffle.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={'Shuffled list'} value={result} />
|
||||
<ToolTextResult title={t('list.shuffle.resultTitle')} value={result} />
|
||||
}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Input list separator',
|
||||
title: t('list.shuffle.inputListSeparator'),
|
||||
component: (
|
||||
<Box>
|
||||
{splitOperators.map(({ title, description, type }) => (
|
||||
|
|
@ -77,7 +83,7 @@ export default function Shuffle() {
|
|||
/>
|
||||
))}
|
||||
<TextFieldWithDesc
|
||||
description={'Set a delimiting symbol or regular expression.'}
|
||||
description={t('list.shuffle.delimiterDescription')}
|
||||
value={values.splitSeparator}
|
||||
onOwnChange={(val) => updateField('splitSeparator', val)}
|
||||
/>
|
||||
|
|
@ -85,11 +91,11 @@ export default function Shuffle() {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Shuffled List Length',
|
||||
title: t('list.shuffle.shuffledListLength'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description={'Output this many random items'}
|
||||
description={t('list.shuffle.outputLengthDescription')}
|
||||
value={values.length}
|
||||
onOwnChange={(val) => updateField('length', val)}
|
||||
/>
|
||||
|
|
@ -97,13 +103,13 @@ export default function Shuffle() {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Shuffled List Separator',
|
||||
title: t('list.shuffle.shuffledListSeparator'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.joinSeparator}
|
||||
onOwnChange={(value) => updateField('joinSeparator', value)}
|
||||
description={'Use this separator in the randomized list.'}
|
||||
description={t('list.shuffle.joinSeparatorDescription')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,13 @@ export const tool = defineTool('list', {
|
|||
path: 'shuffle',
|
||||
icon: 'material-symbols-light:shuffle',
|
||||
description:
|
||||
'A tool to randomly reorder items in a list. Perfect for randomizing data, creating random selections, or generating random sequences.',
|
||||
shortDescription: 'Randomly reorder list items.',
|
||||
"World's simplest browser-based utility for shuffling list items. Input your list and instantly get a randomized version with items in random order. Perfect for creating variety, testing randomness, or mixing up ordered data.",
|
||||
shortDescription: 'Randomize the order of list items',
|
||||
keywords: ['shuffle'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.shuffle.name',
|
||||
description: 'list.shuffle.description',
|
||||
shortDescription: 'list.shuffle.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
|||
import SelectWithDesc from '@components/options/SelectWithDesc';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
splitSeparatorType: 'symbol' as SplitOperatorType,
|
||||
|
|
@ -37,6 +38,7 @@ const splitOperators: {
|
|||
];
|
||||
|
||||
export default function SplitText({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||
|
|
@ -69,26 +71,34 @@ export default function SplitText({ title }: ToolComponentProps) {
|
|||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input list'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('list.sort.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('list.sort.resultTitle')} value={result} />
|
||||
}
|
||||
resultComponent={<ToolTextResult title={'Sorted list'} value={result} />}
|
||||
initialValues={initialValues}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Input item separator',
|
||||
title: t('list.sort.inputItemSeparator'),
|
||||
component: (
|
||||
<Box>
|
||||
{splitOperators.map(({ title, description, type }) => (
|
||||
<SimpleRadio
|
||||
key={type}
|
||||
onClick={() => updateField('splitSeparatorType', type)}
|
||||
title={title}
|
||||
description={description}
|
||||
title={t(`list.sort.splitOperators.${type}.title`)}
|
||||
description={t(
|
||||
`list.sort.splitOperators.${type}.description`
|
||||
)}
|
||||
checked={values.splitSeparatorType === type}
|
||||
/>
|
||||
))}
|
||||
<TextFieldWithDesc
|
||||
description={'Set a delimiting symbol or regular expression.'}
|
||||
description={t('list.sort.splitSeparatorDescription')}
|
||||
value={values.splitSeparator}
|
||||
onOwnChange={(val) => updateField('splitSeparator', val)}
|
||||
/>
|
||||
|
|
@ -96,35 +106,45 @@ export default function SplitText({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Sort method',
|
||||
title: t('list.sort.sortMethod'),
|
||||
component: (
|
||||
<Box>
|
||||
<SelectWithDesc
|
||||
selected={values.sortingMethod}
|
||||
options={[
|
||||
{ label: 'Sort Alphabetically', value: 'alphabetic' },
|
||||
{ label: 'Sort Numerically', value: 'numeric' },
|
||||
{ label: 'Sort by Length', value: 'length' }
|
||||
{
|
||||
label: t('list.sort.sortOptions.alphabetic'),
|
||||
value: 'alphabetic'
|
||||
},
|
||||
{
|
||||
label: t('list.sort.sortOptions.numeric'),
|
||||
value: 'numeric'
|
||||
},
|
||||
{ label: t('list.sort.sortOptions.length'), value: 'length' }
|
||||
]}
|
||||
onChange={(value) => updateField('sortingMethod', value)}
|
||||
description={'Select a sorting method.'}
|
||||
description={t('list.sort.sortMethodDescription')}
|
||||
/>
|
||||
<SelectWithDesc
|
||||
selected={values.increasing}
|
||||
options={[
|
||||
{ label: 'Increasing order', value: true },
|
||||
{ label: 'Decreasing order', value: false }
|
||||
{
|
||||
label: t('list.sort.orderOptions.increasing'),
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: t('list.sort.orderOptions.decreasing'),
|
||||
value: false
|
||||
}
|
||||
]}
|
||||
onChange={(value) => {
|
||||
updateField('increasing', value);
|
||||
}}
|
||||
description={'Select a sorting order.'}
|
||||
description={t('list.sort.orderDescription')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Case Sensitive Sort'}
|
||||
description={
|
||||
'Sort uppercase and lowercase items separately. Capital letters precede lowercase letters in an ascending list. (Works only in alphabetical sorting mode.)'
|
||||
}
|
||||
title={t('list.sort.caseSensitive')}
|
||||
description={t('list.sort.caseSensitiveDescription')}
|
||||
checked={values.caseSensitive}
|
||||
onChange={(val) => updateField('caseSensitive', val)}
|
||||
/>
|
||||
|
|
@ -132,19 +152,17 @@ export default function SplitText({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Sorted item properties',
|
||||
title: t('list.sort.sortedItemProperties'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description={
|
||||
'Use this symbol as a joiner between items in a sorted list.'
|
||||
}
|
||||
description={t('list.sort.joinSeparatorDescription')}
|
||||
value={values.joinSeparator}
|
||||
onOwnChange={(val) => updateField('joinSeparator', val)}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
title={'Remove duplicates'}
|
||||
description={'Delete duplicate list items.'}
|
||||
title={t('list.sort.removeDuplicates')}
|
||||
description={t('list.sort.removeDuplicatesDescription')}
|
||||
checked={values.removeDuplicated}
|
||||
onChange={(val) => updateField('removeDuplicated', val)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('list', {
|
||||
name: 'Sort',
|
||||
path: 'sort',
|
||||
icon: 'basil:sort-outline',
|
||||
icon: 'material-symbols-light:sort',
|
||||
description:
|
||||
'This is a super simple browser-based application that sorts items in a list and arranges them in increasing or decreasing order. You can sort the items alphabetically, numerically, or by their length. You can also remove duplicate and empty items, as well as trim individual items that have whitespace around them. You can use any separator character to separate the input list items or alternatively use a regular expression to separate them. Additionally, you can create a new delimiter for the sorted output list.',
|
||||
shortDescription: 'Quickly sort a list',
|
||||
"World's simplest browser-based utility for sorting list items. Input your list and specify sorting criteria to organize items in ascending or descending order. Perfect for data organization, text processing, or creating ordered lists.",
|
||||
shortDescription: 'Sort list items in specified order',
|
||||
keywords: ['sort'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.sort.name',
|
||||
description: 'list.sort.description',
|
||||
shortDescription: 'list.sort.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('list', {
|
||||
name: 'Truncate',
|
||||
path: 'truncate',
|
||||
icon: 'mdi:format-horizontal-align-right',
|
||||
icon: 'material-symbols-light:content-cut',
|
||||
description:
|
||||
"World's simplest browser-based utility for truncating lists. Quickly limit the number of items in your list by specifying a maximum length. Perfect for sampling data, creating previews, or managing large lists. Supports custom separators and various truncation options.",
|
||||
shortDescription: 'Limit the number of items in a list',
|
||||
"World's simplest browser-based utility for truncating lists. Input your list and specify the maximum number of items to keep. Perfect for data processing, list management, or limiting content length.",
|
||||
shortDescription: 'Truncate list to specified number of items',
|
||||
keywords: ['truncate'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.truncate.name',
|
||||
description: 'list.truncate.description',
|
||||
shortDescription: 'list.truncate.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('list', {
|
||||
name: 'Unwrap',
|
||||
path: 'unwrap',
|
||||
icon: 'mdi:unwrap',
|
||||
icon: 'material-symbols-light:unfold-more',
|
||||
description:
|
||||
'A tool to remove characters from the beginning and end of each item in a list. Perfect for cleaning up formatted data or removing unwanted wrappers.',
|
||||
shortDescription: 'Remove characters around list items.',
|
||||
"World's simplest browser-based utility for unwrapping list items. Input your wrapped list and specify unwrapping criteria to flatten organized items. Perfect for data processing, text manipulation, or extracting content from structured lists.",
|
||||
shortDescription: 'Unwrap list items from structured format',
|
||||
keywords: ['unwrap'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.unwrap.name',
|
||||
description: 'list.unwrap.description',
|
||||
shortDescription: 'list.unwrap.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
|||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import * as Yup from 'yup';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface InitialValuesType {
|
||||
splitOperatorType: SplitOperatorType;
|
||||
|
|
@ -85,6 +86,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function Wrap({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -117,50 +119,50 @@ export default function Wrap({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Split Options',
|
||||
title: t('list.wrap.splitOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('splitOperatorType', 'symbol')}
|
||||
checked={values.splitOperatorType === 'symbol'}
|
||||
title={'Split by Symbol'}
|
||||
title={t('list.wrap.splitBySymbol')}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('splitOperatorType', 'regex')}
|
||||
checked={values.splitOperatorType === 'regex'}
|
||||
title={'Split by Regular Expression'}
|
||||
title={t('list.wrap.splitByRegex')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.splitSeparator}
|
||||
onOwnChange={(val) => updateField('splitSeparator', val)}
|
||||
description={'Separator to split the list'}
|
||||
description={t('list.wrap.splitSeparatorDescription')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.joinSeparator}
|
||||
onOwnChange={(val) => updateField('joinSeparator', val)}
|
||||
description={'Separator to join the wrapped list'}
|
||||
description={t('list.wrap.joinSeparatorDescription')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.deleteEmptyItems}
|
||||
onChange={(checked) => updateField('deleteEmptyItems', checked)}
|
||||
title={'Remove empty items'}
|
||||
title={t('list.wrap.removeEmptyItems')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Wrap Options',
|
||||
title: t('list.wrap.wrapOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.left}
|
||||
onOwnChange={(val) => updateField('left', val)}
|
||||
description={'Text to add before each item'}
|
||||
description={t('list.wrap.leftTextDescription')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.right}
|
||||
onOwnChange={(val) => updateField('right', val)}
|
||||
description={'Text to add after each item'}
|
||||
description={t('list.wrap.rightTextDescription')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -171,16 +173,21 @@ export default function Wrap({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input List" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('list.wrap.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('list.wrap.resultTitle')} value={result} />
|
||||
}
|
||||
resultComponent={<ToolTextResult title="Wrapped List" value={result} />}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
validationSchema={validationSchema}
|
||||
toolInfo={{
|
||||
title: 'List Wrapping',
|
||||
description:
|
||||
"This tool allows you to add text before and after each item in a list. You can specify different text for the left and right sides, and control how the list is processed. It's useful for adding quotes, brackets, or other formatting to list items, preparing data for different formats, or creating structured text."
|
||||
title: t('list.wrap.toolInfo.title'),
|
||||
description: t('list.wrap.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('list', {
|
||||
name: 'Wrap',
|
||||
path: 'wrap',
|
||||
icon: 'mdi:wrap',
|
||||
icon: 'material-symbols-light:wrap-text',
|
||||
description:
|
||||
'A tool to wrap each item in a list with custom prefix and suffix characters. Useful for formatting lists for code, markup languages, or presentation.',
|
||||
shortDescription: 'Add characters around list items.',
|
||||
"World's simplest browser-based utility for wrapping list items. Input your list and specify wrapping criteria to organize items into logical groups. Perfect for categorizing data, organizing information, or creating structured lists.",
|
||||
shortDescription: 'Wrap list items with specified criteria',
|
||||
keywords: ['wrap'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'list.wrap.name',
|
||||
description: 'list.wrap.description',
|
||||
shortDescription: 'list.wrap.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { generateArithmeticSequence } from './service';
|
|||
import * as Yup from 'yup';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type InitialValuesType = {
|
||||
firstTerm: string;
|
||||
|
|
@ -70,6 +71,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function ArithmeticSequence({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
return (
|
||||
|
|
@ -77,35 +79,43 @@ export default function ArithmeticSequence({ title }: ToolComponentProps) {
|
|||
title={title}
|
||||
inputComponent={null}
|
||||
resultComponent={
|
||||
<ToolTextResult title="Generated Sequence" value={result} />
|
||||
<ToolTextResult
|
||||
title={t('number.arithmeticSequence.resultTitle')}
|
||||
value={result}
|
||||
/>
|
||||
}
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
exampleCards={exampleCards}
|
||||
toolInfo={{
|
||||
title: 'What is an Arithmetic Sequence?',
|
||||
description:
|
||||
'An arithmetic sequence is a sequence of numbers where the difference between each consecutive term is constant. This constant difference is called the common difference. Given the first term (a₁) and the common difference (d), each term can be found by adding the common difference to the previous term.'
|
||||
title: t('number.arithmeticSequence.toolInfo.title'),
|
||||
description: t('number.arithmeticSequence.toolInfo.description')
|
||||
}}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Sequence Parameters',
|
||||
title: t('number.arithmeticSequence.sequenceParameters'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description="First term of the sequence (a₁)"
|
||||
description={t(
|
||||
'number.arithmeticSequence.firstTermDescription'
|
||||
)}
|
||||
value={values.firstTerm}
|
||||
onOwnChange={(val) => updateField('firstTerm', val)}
|
||||
type="number"
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Common difference between terms (d)"
|
||||
description={t(
|
||||
'number.arithmeticSequence.commonDifferenceDescription'
|
||||
)}
|
||||
value={values.commonDifference}
|
||||
onOwnChange={(val) => updateField('commonDifference', val)}
|
||||
type="number"
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Number of terms to generate (n)"
|
||||
description={t(
|
||||
'number.arithmeticSequence.numberOfTermsDescription'
|
||||
)}
|
||||
value={values.numberOfTerms}
|
||||
onOwnChange={(val) => updateField('numberOfTerms', val)}
|
||||
type="number"
|
||||
|
|
@ -114,10 +124,10 @@ export default function ArithmeticSequence({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Output Format',
|
||||
title: t('number.arithmeticSequence.outputFormat'),
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
description="Separator between terms"
|
||||
description={t('number.arithmeticSequence.separatorDescription')}
|
||||
value={values.separator}
|
||||
onOwnChange={(val) => updateField('separator', val)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,20 +2,17 @@ import { defineTool } from '@tools/defineTool';
|
|||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('number', {
|
||||
name: 'Generate Arithmetic Sequence',
|
||||
name: 'Arithmetic Sequence',
|
||||
path: 'arithmetic-sequence',
|
||||
icon: 'ic:sharp-plus',
|
||||
icon: 'material-symbols:functions',
|
||||
description:
|
||||
'Generate an arithmetic sequence by specifying the first term (a₁), common difference (d), and number of terms (n). The tool creates a sequence where each number differs from the previous by a constant difference.',
|
||||
shortDescription:
|
||||
'Generate a sequence where each term differs by a constant value.',
|
||||
keywords: [
|
||||
'arithmetic',
|
||||
'sequence',
|
||||
'progression',
|
||||
'numbers',
|
||||
'series',
|
||||
'generate'
|
||||
],
|
||||
component: lazy(() => import('./index'))
|
||||
'Generate arithmetic sequences with specified start value, common difference, and number of terms. Create mathematical progressions for calculations or analysis.',
|
||||
shortDescription: 'Generate arithmetic sequences',
|
||||
keywords: ['arithmetic', 'sequence', 'math', 'progression'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'number.arithmeticSequence.name',
|
||||
description: 'number.arithmeticSequence.description',
|
||||
shortDescription: 'number.arithmeticSequence.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { listOfIntegers } from './service';
|
|||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
firstValue: '1',
|
||||
|
|
@ -14,6 +15,7 @@ const initialValues = {
|
|||
};
|
||||
|
||||
export default function GenerateNumbers({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
const compute = (optionsValues: typeof initialValues) => {
|
||||
|
|
@ -34,23 +36,23 @@ export default function GenerateNumbers({ title }: ToolComponentProps) {
|
|||
initialValues={initialValues}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Arithmetic sequence option',
|
||||
title: t('number.generate.arithmeticSequenceOption'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description={'Start sequence from this number.'}
|
||||
description={t('number.generate.startSequenceDescription')}
|
||||
value={values.firstValue}
|
||||
onOwnChange={(val) => updateField('firstValue', val)}
|
||||
type={'number'}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description={'Increase each element by this amount'}
|
||||
description={t('number.generate.stepDescription')}
|
||||
value={values.step}
|
||||
onOwnChange={(val) => updateField('step', val)}
|
||||
type={'number'}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description={'Number of elements in sequence.'}
|
||||
description={t('number.generate.numberOfElementsDescription')}
|
||||
value={values.numberOfNumbers}
|
||||
onOwnChange={(val) => updateField('numberOfNumbers', val)}
|
||||
type={'number'}
|
||||
|
|
@ -59,12 +61,10 @@ export default function GenerateNumbers({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Separator',
|
||||
title: t('number.generate.separator'),
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
description={
|
||||
'Separate elements in the arithmetic sequence by this character.'
|
||||
}
|
||||
description={t('number.generate.separatorDescription')}
|
||||
value={values.separator}
|
||||
onOwnChange={(val) => updateField('separator', val)}
|
||||
/>
|
||||
|
|
@ -73,7 +73,10 @@ export default function GenerateNumbers({ title }: ToolComponentProps) {
|
|||
]}
|
||||
compute={compute}
|
||||
resultComponent={
|
||||
<ToolTextResult title={'Generated numbers'} value={result} />
|
||||
<ToolTextResult
|
||||
title={t('number.generate.resultTitle')}
|
||||
value={result}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@ import { lazy } from 'react';
|
|||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('number', {
|
||||
name: 'Generate numbers',
|
||||
name: 'Generate',
|
||||
path: 'generate',
|
||||
shortDescription: 'Quickly calculate a list of integers in your browser',
|
||||
icon: 'lsicon:number-filled',
|
||||
icon: 'material-symbols:add-circle',
|
||||
description:
|
||||
'Quickly calculate a list of integers in your browser. To get your list, just specify the first integer, change value and total count in the options below, and this utility will generate that many integers',
|
||||
keywords: ['generate'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Generate random numbers within specified ranges. Create sequences of numbers for testing, simulations, or random data generation.',
|
||||
shortDescription: 'Generate random numbers in specified ranges',
|
||||
keywords: ['generate', 'random', 'numbers'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'number.generate.name',
|
||||
description: 'number.generate.description',
|
||||
shortDescription: 'number.generate.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
45
src/pages/tools/number/i18n/en.json
Normal file
45
src/pages/tools/number/i18n/en.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"sum": {
|
||||
"title": "Sum Numbers",
|
||||
"description": "Calculate the sum of a list of numbers.",
|
||||
"inputTitle": "Input numbers",
|
||||
"resultTitle": "Sum",
|
||||
"sumOptions": "Sum Options",
|
||||
"ignoreNonNumeric": "Ignore non-numeric values",
|
||||
"ignoreNonNumericDescription": "Skip values that are not numbers",
|
||||
"toolInfo": {
|
||||
"title": "Sum numbers",
|
||||
"description": "This tool allows you to calculate the sum of a list of numbers. You can input numbers separated by various delimiters and get their total sum."
|
||||
}
|
||||
},
|
||||
"generate": {
|
||||
"title": "Generate Numbers",
|
||||
"description": "Generate a sequence of numbers with customizable parameters.",
|
||||
"resultTitle": "Generated numbers",
|
||||
"arithmeticSequenceOption": "Arithmetic sequence option",
|
||||
"startSequenceDescription": "Start sequence from this number.",
|
||||
"stepDescription": "Increase each element by this amount",
|
||||
"numberOfElementsDescription": "Number of elements in sequence.",
|
||||
"separator": "Separator",
|
||||
"separatorDescription": "Separate elements in the arithmetic sequence by this character.",
|
||||
"toolInfo": {
|
||||
"title": "Generate numbers",
|
||||
"description": "This tool allows you to generate a sequence of numbers with customizable parameters. You can specify the starting value, step size, and number of elements."
|
||||
}
|
||||
},
|
||||
"arithmeticSequence": {
|
||||
"title": "Arithmetic Sequence",
|
||||
"description": "Generate arithmetic sequences with customizable parameters.",
|
||||
"resultTitle": "Generated Sequence",
|
||||
"sequenceParameters": "Sequence Parameters",
|
||||
"firstTermDescription": "First term of the sequence (a₁)",
|
||||
"commonDifferenceDescription": "Common difference between terms (d)",
|
||||
"numberOfTermsDescription": "Number of terms to generate (n)",
|
||||
"outputFormat": "Output Format",
|
||||
"separatorDescription": "Separator between terms",
|
||||
"toolInfo": {
|
||||
"title": "What is an Arithmetic Sequence?",
|
||||
"description": "An arithmetic sequence is a sequence of numbers where the difference between each consecutive term is constant. This constant difference is called the common difference. Given the first term (a₁) and the common difference (d), each term can be found by adding the common difference to the previous term."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
|||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
extractionType: 'smart' as NumberExtractionType,
|
||||
|
|
@ -118,6 +119,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function SumNumbers({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -126,16 +128,16 @@ export default function SumNumbers({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Number extraction',
|
||||
title: t('number.sum.numberExtraction'),
|
||||
component: extractionTypes.map(
|
||||
({ title, description, type, withTextField, textValueAccessor }) =>
|
||||
withTextField ? (
|
||||
<RadioWithTextField
|
||||
key={type}
|
||||
checked={type === values.extractionType}
|
||||
title={title}
|
||||
title={t(`number.sum.extractionTypes.${type}.title`)}
|
||||
fieldName={'extractionType'}
|
||||
description={description}
|
||||
description={t(`number.sum.extractionTypes.${type}.description`)}
|
||||
value={
|
||||
textValueAccessor ? values[textValueAccessor].toString() : ''
|
||||
}
|
||||
|
|
@ -149,18 +151,18 @@ export default function SumNumbers({ title }: ToolComponentProps) {
|
|||
key={title}
|
||||
onClick={() => updateField('extractionType', type)}
|
||||
checked={values.extractionType === type}
|
||||
description={description}
|
||||
title={title}
|
||||
description={t(`number.sum.extractionTypes.${type}.description`)}
|
||||
title={t(`number.sum.extractionTypes.${type}.title`)}
|
||||
/>
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Running Sum',
|
||||
title: t('number.sum.runningSum'),
|
||||
component: (
|
||||
<CheckboxWithDesc
|
||||
title={'Print Running Sum'}
|
||||
description={"Display the sum as it's calculated step by step."}
|
||||
title={t('number.sum.printRunningSum')}
|
||||
description={t('number.sum.printRunningSumDescription')}
|
||||
checked={values.printRunningSum}
|
||||
onChange={(value) => updateField('printRunningSum', value)}
|
||||
/>
|
||||
|
|
@ -171,8 +173,16 @@ export default function SumNumbers({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
input={input}
|
||||
inputComponent={<ToolTextInput value={input} onChange={setInput} />}
|
||||
resultComponent={<ToolTextResult title={'Total'} value={result} />}
|
||||
inputComponent={
|
||||
<ToolTextInput
|
||||
title={t('number.sum.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('number.sum.resultTitle')} value={result} />
|
||||
}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
compute={(optionsValues, input) => {
|
||||
|
|
@ -181,9 +191,8 @@ export default function SumNumbers({ title }: ToolComponentProps) {
|
|||
}}
|
||||
setInput={setInput}
|
||||
toolInfo={{
|
||||
title: 'What Is a Number Sum Calculator?',
|
||||
description:
|
||||
'This is an online browser-based utility for calculating the sum of a bunch of numbers. You can enter the numbers separated by a comma, space, or any other character, including the line break. You can also simply paste a fragment of textual data that contains numerical values that you want to sum up and the utility will extract them and find their sum.'
|
||||
title: t('number.sum.toolInfo.title'),
|
||||
description: t('number.sum.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,17 @@ import { lazy } from 'react';
|
|||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('number', {
|
||||
name: 'Number Sum Calculator',
|
||||
name: 'Sum',
|
||||
path: 'sum',
|
||||
icon: 'fluent:autosum-20-regular',
|
||||
icon: 'material-symbols:add',
|
||||
description:
|
||||
'Quickly calculate the sum of numbers in your browser. To get your sum, just enter your list of numbers in the input field, adjust the separator between the numbers in the options below, and this utility will add up all these numbers.',
|
||||
shortDescription: 'Quickly sum numbers',
|
||||
keywords: ['sum'],
|
||||
component: lazy(() => import('./index'))
|
||||
'Calculate the sum of a list of numbers. Enter numbers separated by commas or newlines to get their total sum.',
|
||||
shortDescription: 'Calculate sum of numbers',
|
||||
keywords: ['sum', 'add', 'calculate', 'total'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'number.sum.name',
|
||||
description: 'number.sum.description',
|
||||
shortDescription: 'number.sum.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
import { Box, Typography } from '@mui/material';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { compressPdf } from './service';
|
||||
import { InitialValuesType, CompressionLevel } from './types';
|
||||
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import ToolFileResult from '@components/result/ToolFileResult';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { CompressionLevel, InitialValuesType } from './types';
|
||||
import { compressPdf } from './service';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import { CustomSnackBarContext } from '../../../../contexts/CustomSnackBarContext';
|
||||
import { CustomSnackBarContext } from '@contexts/CustomSnackBarContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
compressionLevel: 'medium'
|
||||
compressionLevel: 'low'
|
||||
};
|
||||
|
||||
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
{
|
||||
title: 'Low Compression',
|
||||
description: 'Slightly reduce file size with minimal quality loss',
|
||||
description: 'Minimal quality loss with slight file size reduction',
|
||||
sampleText: '',
|
||||
sampleResult: '',
|
||||
sampleOptions: {
|
||||
|
|
@ -49,6 +51,7 @@ export default function CompressPdf({
|
|||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
const [resultSize, setResultSize] = useState<string>('');
|
||||
|
|
@ -77,10 +80,7 @@ export default function CompressPdf({
|
|||
} catch (error) {
|
||||
console.error('Error getting PDF info:', error);
|
||||
setFileInfo(null);
|
||||
showSnackBar(
|
||||
'Error reading PDF file. Please make sure it is a valid PDF.',
|
||||
'error'
|
||||
);
|
||||
showSnackBar(t('pdf.compressPdf.errorReadingPdf'), 'error');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -112,9 +112,9 @@ export default function CompressPdf({
|
|||
} catch (error) {
|
||||
console.error('Error compressing PDF:', error);
|
||||
showSnackBar(
|
||||
`Failed to compress PDF: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
t('pdf.compressPdf.errorCompressingPdf', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
}),
|
||||
'error'
|
||||
);
|
||||
setResult(null);
|
||||
|
|
@ -130,18 +130,18 @@ export default function CompressPdf({
|
|||
}[] = [
|
||||
{
|
||||
value: 'low',
|
||||
label: 'Low Compression',
|
||||
description: 'Slightly reduce file size with minimal quality loss'
|
||||
label: t('pdf.compressPdf.lowCompression'),
|
||||
description: t('pdf.compressPdf.lowCompressionDescription')
|
||||
},
|
||||
{
|
||||
value: 'medium',
|
||||
label: 'Medium Compression',
|
||||
description: 'Balance between file size and quality'
|
||||
label: t('pdf.compressPdf.mediumCompression'),
|
||||
description: t('pdf.compressPdf.mediumCompressionDescription')
|
||||
},
|
||||
{
|
||||
value: 'high',
|
||||
label: 'High Compression',
|
||||
description: 'Maximum file size reduction with some quality loss'
|
||||
label: t('pdf.compressPdf.highCompression'),
|
||||
description: t('pdf.compressPdf.highCompressionDescription')
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -157,26 +157,26 @@ export default function CompressPdf({
|
|||
value={input}
|
||||
onChange={setInput}
|
||||
accept={['application/pdf']}
|
||||
title={'Input PDF'}
|
||||
title={t('pdf.compressPdf.inputTitle')}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolFileResult
|
||||
title={'Compressed PDF'}
|
||||
title={t('pdf.compressPdf.resultTitle')}
|
||||
value={result}
|
||||
extension={'pdf'}
|
||||
loading={isProcessing}
|
||||
loadingText={'Compressing PDF'}
|
||||
loadingText={t('pdf.compressPdf.compressingPdf')}
|
||||
/>
|
||||
}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Compression Settings',
|
||||
title: t('pdf.compressPdf.compressionSettings'),
|
||||
component: (
|
||||
<Box>
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1 }}>
|
||||
Compression Level
|
||||
{t('pdf.compressPdf.compressionLevel')}
|
||||
</Typography>
|
||||
|
||||
{compressionOptions.map((option) => (
|
||||
|
|
@ -201,14 +201,17 @@ export default function CompressPdf({
|
|||
}}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
File size: <strong>{fileInfo.size}</strong>
|
||||
{t('pdf.compressPdf.fileSize')}:{' '}
|
||||
<strong>{fileInfo.size}</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Pages: <strong>{fileInfo.pages}</strong>
|
||||
{t('pdf.compressPdf.pages')}:{' '}
|
||||
<strong>{fileInfo.pages}</strong>
|
||||
</Typography>
|
||||
{resultSize && (
|
||||
<Typography variant="body2">
|
||||
Compressed file size: <strong>{resultSize}</strong>
|
||||
{t('pdf.compressPdf.compressedFileSize')}:{' '}
|
||||
<strong>{resultSize}</strong>
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -24,5 +24,10 @@ export const tool = defineTool('pdf', {
|
|||
],
|
||||
longDescription:
|
||||
'Compress PDF files securely in your browser using Ghostscript. Your files never leave your device, ensuring complete privacy while reducing file sizes for email sharing, uploading to websites, or saving storage space. Powered by WebAssembly technology.',
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'pdf.compressPdf.name',
|
||||
description: 'pdf.compressPdf.description',
|
||||
shortDescription: 'pdf.compressPdf.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
72
src/pages/tools/pdf/i18n/en.json
Normal file
72
src/pages/tools/pdf/i18n/en.json
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"mergePdf": {
|
||||
"title": "Merge PDF",
|
||||
"description": "Combine multiple PDF files into a single document.",
|
||||
"inputTitle": "Input PDFs",
|
||||
"resultTitle": "Merged PDF",
|
||||
"mergingPdfs": "Merging PDFs",
|
||||
"pdfOptions": "PDF Options",
|
||||
"sortByFileName": "Sort by file name",
|
||||
"sortByFileNameDescription": "Sort PDFs alphabetically by file name",
|
||||
"sortByUploadOrder": "Sort by upload order",
|
||||
"sortByUploadOrderDescription": "Keep PDFs in the order they were uploaded",
|
||||
"toolInfo": {
|
||||
"title": "Merge PDF Files",
|
||||
"description": "This tool allows you to combine multiple PDF files into a single document. You can choose how to sort the PDFs and the tool will merge them in the specified order."
|
||||
}
|
||||
},
|
||||
"compressPdf": {
|
||||
"title": "Compress PDF",
|
||||
"description": "Reduce PDF file size while maintaining quality.",
|
||||
"inputTitle": "Input PDF",
|
||||
"resultTitle": "Compressed PDF",
|
||||
"compressingPdf": "Compressing PDF",
|
||||
"compressionOptions": "Compression Options",
|
||||
"qualityDescription": "Compression quality (1-100)",
|
||||
"qualityPlaceholder": "Quality",
|
||||
"toolInfo": {
|
||||
"title": "Compress PDF",
|
||||
"description": "This tool allows you to compress PDF files to reduce their size while maintaining acceptable quality. You can adjust the compression level to balance between file size and quality."
|
||||
}
|
||||
},
|
||||
"splitPdf": {
|
||||
"title": "Split PDF",
|
||||
"description": "Extract specific pages from a PDF document.",
|
||||
"inputTitle": "Input PDF",
|
||||
"resultTitle": "Extracted PDF",
|
||||
"extractingPages": "Extracting pages",
|
||||
"pageSelection": "Page Selection",
|
||||
"pdfPageCount": "PDF has {{count}} page{{count !== 1 ? 's' : ''}}",
|
||||
"pageRangesDescription": "Enter page numbers or ranges separated by commas (e.g., 1,3,5-7)",
|
||||
"pageRangesPlaceholder": "e.g., 1,5-8",
|
||||
"pageExtractionPreview": "{{count}} page{{count !== 1 ? 's' : ''}} will be extracted",
|
||||
"toolInfo": {
|
||||
"title": "Split PDF",
|
||||
"description": "This tool allows you to extract specific pages from a PDF document. You can specify individual pages or ranges of pages to extract."
|
||||
}
|
||||
},
|
||||
"rotatePdf": {
|
||||
"title": "Rotate PDF",
|
||||
"description": "Rotate pages in a PDF document.",
|
||||
"inputTitle": "Input PDF",
|
||||
"resultTitle": "Rotated PDF",
|
||||
"rotatingPages": "Rotating pages",
|
||||
"rotationSettings": "Rotation Settings",
|
||||
"rotationAngle": "Rotation Angle",
|
||||
"angleOptions": {
|
||||
"clockwise90": "90° Clockwise",
|
||||
"upsideDown180": "180° (Upside down)",
|
||||
"counterClockwise270": "270° (90° Counter-clockwise)"
|
||||
},
|
||||
"applyToAllPages": "Apply to all pages",
|
||||
"pdfPageCount": "PDF has {{count}} page{{count !== 1 ? 's' : ''}}",
|
||||
"pageRangesDescription": "Enter page numbers or ranges separated by commas (e.g., 1,3,5-7)",
|
||||
"pageRangesPlaceholder": "e.g., 1,5-8",
|
||||
"allPagesWillBeRotated": "All {{count}} pages will be rotated",
|
||||
"pagesWillBeRotated": "{{count}} page{{count !== 1 ? 's' : ''}} will be rotated",
|
||||
"toolInfo": {
|
||||
"title": "How to Use the Rotate PDF Tool",
|
||||
"description": "This tool allows you to rotate pages in a PDF document. You can rotate all pages or specify individual pages to rotate. Choose a rotation angle: 90° Clockwise, 180° (Upside down), or 270° (90° Counter-clockwise). To rotate specific pages, uncheck \"Apply to all pages\" and enter page numbers or ranges separated by commas (e.g., 1,3,5-7)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,10 @@ import { mergePdf } from './service';
|
|||
import ToolMultiPdfInput, {
|
||||
MultiPdfInput
|
||||
} from '@components/input/ToolMultiplePdfInput';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function MergePdf({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<MultiPdfInput[]>([]);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||
|
|
@ -42,24 +44,23 @@ export default function MergePdf({ title }: ToolComponentProps) {
|
|||
setInput(pdfInputs);
|
||||
}}
|
||||
accept={['application/pdf']}
|
||||
title={'Input PDF'}
|
||||
title={t('pdf.merge.inputTitle')}
|
||||
type="pdf"
|
||||
/>
|
||||
}
|
||||
getGroups={null}
|
||||
resultComponent={
|
||||
<ToolFileResult
|
||||
title={'Output merged PDF'}
|
||||
title={t('pdf.merge.resultTitle')}
|
||||
value={result}
|
||||
extension={'pdf'}
|
||||
loading={isProcessing}
|
||||
loadingText={'Extracting pages'}
|
||||
loadingText={t('pdf.merge.loadingText')}
|
||||
/>
|
||||
}
|
||||
toolInfo={{
|
||||
title: 'How to Use the Merge PDF Tool?',
|
||||
description: `This tool allows you to merge multiple PDF files into a single document.
|
||||
To use the tool, simply upload the PDF files you want to merge. The tool will then combine all pages from the input files into a single PDF document.`
|
||||
title: t('pdf.merge.toolInfo.title'),
|
||||
description: t('pdf.merge.toolInfo.description')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,5 +8,10 @@ export const meta = defineTool('pdf', {
|
|||
icon: 'material-symbols-light:merge',
|
||||
component: lazy(() => import('./index')),
|
||||
keywords: ['pdf', 'merge', 'extract', 'pages', 'combine', 'document'],
|
||||
path: 'merge-pdf'
|
||||
path: 'merge-pdf',
|
||||
i18n: {
|
||||
name: 'pdf.mergePdf.name',
|
||||
description: 'pdf.mergePdf.description',
|
||||
shortDescription: 'pdf.mergePdf.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,5 +9,10 @@ export const meta = defineTool('pdf', {
|
|||
icon: 'material-symbols:import-contacts',
|
||||
component: lazy(() => import('./index')),
|
||||
keywords: ['pdf', 'epub', 'convert', 'ebook'],
|
||||
path: 'pdf-to-epub'
|
||||
path: 'pdf-to-epub',
|
||||
i18n: {
|
||||
name: 'pdf.pdfToEpub.name',
|
||||
description: 'pdf.pdfToEpub.description',
|
||||
shortDescription: 'pdf.pdfToEpub.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,5 +23,10 @@ export const tool = defineTool('pdf', {
|
|||
],
|
||||
longDescription:
|
||||
'Add password protection to your PDF files securely in your browser. Your files never leave your device, ensuring complete privacy while securing your documents with password encryption. Perfect for protecting sensitive information, confidential documents, or personal data.',
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'pdf.protectPdf.name',
|
||||
description: 'pdf.protectPdf.description',
|
||||
shortDescription: 'pdf.protectPdf.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { Box, FormControlLabel, Switch, Typography } from '@mui/material';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Typography, FormControlLabel, Switch } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ToolFileResult from '@components/result/ToolFileResult';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||
import ToolFileResult from '@components/result/ToolFileResult';
|
||||
import { parsePageRanges, rotatePdf } from './service';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { InitialValuesType, RotationAngle } from './types';
|
||||
import { parsePageRanges, rotatePdf } from './service';
|
||||
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import { isArray } from 'lodash';
|
||||
import { InitialValuesType, RotationAngle } from './types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
rotationAngle: 90,
|
||||
|
|
@ -21,7 +21,7 @@ const initialValues: InitialValuesType = {
|
|||
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
{
|
||||
title: 'Rotate All Pages 90°',
|
||||
description: 'Rotate all pages in the document 90 degrees clockwise',
|
||||
description: 'Rotate all pages in the PDF by 90 degrees clockwise',
|
||||
sampleText: '',
|
||||
sampleResult: '',
|
||||
sampleOptions: {
|
||||
|
|
@ -32,7 +32,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
},
|
||||
{
|
||||
title: 'Rotate Specific Pages 180°',
|
||||
description: 'Rotate only pages 1 and 3 by 180 degrees',
|
||||
description: 'Rotate pages 1 and 3 by 180 degrees',
|
||||
sampleText: '',
|
||||
sampleResult: '',
|
||||
sampleOptions: {
|
||||
|
|
@ -58,6 +58,7 @@ export default function RotatePdf({
|
|||
title,
|
||||
longDescription
|
||||
}: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||
|
|
@ -90,7 +91,9 @@ export default function RotatePdf({
|
|||
|
||||
if (applyToAllPages) {
|
||||
setPageRangePreview(
|
||||
totalPages > 0 ? `All ${totalPages} pages will be rotated` : ''
|
||||
totalPages > 0
|
||||
? t('pdf.rotatePdf.allPagesWillBeRotated', { count: totalPages })
|
||||
: ''
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -102,9 +105,7 @@ export default function RotatePdf({
|
|||
|
||||
try {
|
||||
const count = parsePageRanges(pageRanges, totalPages).length;
|
||||
setPageRangePreview(
|
||||
`${count} page${count !== 1 ? 's' : ''} will be rotated`
|
||||
);
|
||||
setPageRangePreview(t('pdf.rotatePdf.pagesWillBeRotated', { count }));
|
||||
} catch (error) {
|
||||
setPageRangePreview('');
|
||||
}
|
||||
|
|
@ -124,9 +125,9 @@ export default function RotatePdf({
|
|||
}
|
||||
};
|
||||
const angleOptions: { value: RotationAngle; label: string }[] = [
|
||||
{ value: 90, label: '90° Clockwise' },
|
||||
{ value: 180, label: '180° (Upside down)' },
|
||||
{ value: 270, label: '270° (90° Counter-clockwise)' }
|
||||
{ value: 90, label: t('pdf.rotatePdf.angleOptions.clockwise90') },
|
||||
{ value: 180, label: t('pdf.rotatePdf.angleOptions.upsideDown180') },
|
||||
{ value: 270, label: t('pdf.rotatePdf.angleOptions.counterClockwise270') }
|
||||
];
|
||||
return (
|
||||
<ToolContent
|
||||
|
|
@ -141,25 +142,25 @@ export default function RotatePdf({
|
|||
value={input}
|
||||
onChange={setInput}
|
||||
accept={['application/pdf']}
|
||||
title={'Input PDF'}
|
||||
title={t('pdf.rotatePdf.inputTitle')}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolFileResult
|
||||
title={'Rotated PDF'}
|
||||
title={t('pdf.rotatePdf.resultTitle')}
|
||||
value={result}
|
||||
extension={'pdf'}
|
||||
loading={isProcessing}
|
||||
loadingText={'Rotating pages'}
|
||||
loadingText={t('pdf.rotatePdf.rotatingPages')}
|
||||
/>
|
||||
}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Rotation Settings',
|
||||
title: t('pdf.rotatePdf.rotationSettings'),
|
||||
component: (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1 }}>
|
||||
Rotation Angle
|
||||
{t('pdf.rotatePdf.rotationAngle')}
|
||||
</Typography>
|
||||
{angleOptions.map((angleOption) => (
|
||||
<SimpleRadio
|
||||
|
|
@ -182,7 +183,7 @@ export default function RotatePdf({
|
|||
}}
|
||||
/>
|
||||
}
|
||||
label="Apply to all pages"
|
||||
label={t('pdf.rotatePdf.applyToAllPages')}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
|
|
@ -190,7 +191,7 @@ export default function RotatePdf({
|
|||
<Box sx={{ mt: 2 }}>
|
||||
{totalPages > 0 && (
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
PDF has {totalPages} page{totalPages !== 1 ? 's' : ''}
|
||||
{t('pdf.rotatePdf.pdfPageCount', { count: totalPages })}
|
||||
</Typography>
|
||||
)}
|
||||
<TextFieldWithDesc
|
||||
|
|
@ -198,10 +199,8 @@ export default function RotatePdf({
|
|||
onOwnChange={(val) => {
|
||||
updateField('pageRanges', val);
|
||||
}}
|
||||
description={
|
||||
'Enter page numbers or ranges separated by commas (e.g., 1,3,5-7)'
|
||||
}
|
||||
placeholder={'e.g., 1,5-8'}
|
||||
description={t('pdf.rotatePdf.pageRangesDescription')}
|
||||
placeholder={t('pdf.rotatePdf.pageRangesPlaceholder')}
|
||||
/>
|
||||
{pageRangePreview && (
|
||||
<Typography
|
||||
|
|
@ -219,24 +218,8 @@ export default function RotatePdf({
|
|||
]}
|
||||
onValuesChange={onValuesChange}
|
||||
toolInfo={{
|
||||
title: 'How to Use the Rotate PDF Tool',
|
||||
description: `This tool allows you to rotate pages in a PDF document. You can rotate all pages or specify individual pages to rotate.
|
||||
|
||||
Choose a rotation angle:
|
||||
- 90° Clockwise
|
||||
- 180° (Upside down)
|
||||
- 270° (90° Counter-clockwise)
|
||||
|
||||
To rotate specific pages:
|
||||
1. Uncheck "Apply to all pages"
|
||||
2. Enter page numbers or ranges separated by commas (e.g., 1,3,5-7)
|
||||
|
||||
Examples:
|
||||
- "1,5,9" rotates pages 1, 5, and 9
|
||||
- "1-5" rotates pages 1 through 5
|
||||
- "1,3-5,8-10" rotates pages 1, 3, 4, 5, 8, 9, and 10
|
||||
|
||||
${longDescription}`
|
||||
title: t('pdf.rotatePdf.toolInfo.title'),
|
||||
description: t('pdf.rotatePdf.toolInfo.description')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { parsePageRanges, splitPdf } from './service';
|
|||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import ToolPdfInput from '@components/input/ToolPdfInput';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type InitialValuesType = {
|
||||
pageRanges: string;
|
||||
|
|
@ -48,6 +49,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function SplitPdf({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||
|
|
@ -83,9 +85,7 @@ export default function SplitPdf({ title }: ToolComponentProps) {
|
|||
}
|
||||
try {
|
||||
const count = parsePageRanges(pageRanges, totalPages).length;
|
||||
setPageRangePreview(
|
||||
`${count} page${count !== 1 ? 's' : ''} will be extracted`
|
||||
);
|
||||
setPageRangePreview(t('pdf.splitPdf.pageExtractionPreview', { count }));
|
||||
} catch (error) {
|
||||
setPageRangePreview('');
|
||||
}
|
||||
|
|
@ -118,26 +118,26 @@ export default function SplitPdf({ title }: ToolComponentProps) {
|
|||
value={input}
|
||||
onChange={setInput}
|
||||
accept={['application/pdf']}
|
||||
title={'Input PDF'}
|
||||
title={t('pdf.splitPdf.inputTitle')}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolFileResult
|
||||
title={'Output PDF with selected pages'}
|
||||
title={t('pdf.splitPdf.resultTitle')}
|
||||
value={result}
|
||||
extension={'pdf'}
|
||||
loading={isProcessing}
|
||||
loadingText={'Extracting pages'}
|
||||
loadingText={t('pdf.splitPdf.extractingPages')}
|
||||
/>
|
||||
}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Page Selection',
|
||||
title: t('pdf.splitPdf.pageSelection'),
|
||||
component: (
|
||||
<Box>
|
||||
{totalPages > 0 && (
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
PDF has {totalPages} page{totalPages !== 1 ? 's' : ''}
|
||||
{t('pdf.splitPdf.pdfPageCount', { count: totalPages })}
|
||||
</Typography>
|
||||
)}
|
||||
<TextFieldWithDesc
|
||||
|
|
@ -145,10 +145,8 @@ export default function SplitPdf({ title }: ToolComponentProps) {
|
|||
onOwnChange={(val) => {
|
||||
updateField('pageRanges', val);
|
||||
}}
|
||||
description={
|
||||
'Enter page numbers or ranges separated by commas (e.g., 1,3,5-7)'
|
||||
}
|
||||
placeholder={'e.g., 1,5-8'}
|
||||
description={t('pdf.splitPdf.pageRangesDescription')}
|
||||
placeholder={t('pdf.splitPdf.pageRangesPlaceholder')}
|
||||
/>
|
||||
{pageRangePreview && (
|
||||
<Typography
|
||||
|
|
@ -164,15 +162,8 @@ export default function SplitPdf({ title }: ToolComponentProps) {
|
|||
]}
|
||||
onValuesChange={onValuesChange}
|
||||
toolInfo={{
|
||||
title: 'How to Use the Split PDF Tool',
|
||||
description: `This tool allows you to extract specific pages from a PDF document. You can specify individual page numbers (e.g., 1,3,5) or page ranges (e.g., 2-6) or a combination of both (e.g., 1,3-5,8).
|
||||
|
||||
Leave the page ranges field empty to include all pages from the PDF.
|
||||
|
||||
Examples:
|
||||
- "1,5,9" extracts pages 1, 5, and 9
|
||||
- "1-5" extracts pages 1 through 5
|
||||
- "1,3-5,8-10" extracts pages 1, 3, 4, 5, 8, 9, and 10`
|
||||
title: t('pdf.splitPdf.toolInfo.title'),
|
||||
description: t('pdf.splitPdf.toolInfo.description')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,5 +9,10 @@ export const meta = defineTool('pdf', {
|
|||
icon: 'material-symbols-light:call-split-rounded',
|
||||
component: lazy(() => import('./index')),
|
||||
keywords: ['pdf', 'split', 'extract', 'pages', 'range', 'document'],
|
||||
path: 'split-pdf'
|
||||
path: 'split-pdf',
|
||||
i18n: {
|
||||
name: 'pdf.splitPdf.name',
|
||||
description: 'pdf.splitPdf.description',
|
||||
shortDescription: 'pdf.splitPdf.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { GetGroupsType } from '@components/options/ToolOptions';
|
|||
import { Box } from '@mui/material';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import { InitialValuesType } from './types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues: InitialValuesType = {
|
||||
mode: 'encode'
|
||||
|
|
@ -33,6 +34,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function Base64({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -45,18 +47,18 @@ export default function Base64({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Base64 Options',
|
||||
title: t('string.base64.optionsTitle'),
|
||||
component: (
|
||||
<Box>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('mode', 'encode')}
|
||||
checked={values.mode === 'encode'}
|
||||
title={'Base64 Encode'}
|
||||
title={t('string.base64.encode')}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('mode', 'decode')}
|
||||
checked={values.mode === 'decode'}
|
||||
title={'Base64 Decode'}
|
||||
title={t('string.base64.decode')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -67,15 +69,20 @@ export default function Base64({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input Data" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('string.base64.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('string.base64.resultTitle')} value={result} />
|
||||
}
|
||||
resultComponent={<ToolTextResult title="Result" value={result} />}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
toolInfo={{
|
||||
title: 'What is Base64?',
|
||||
description:
|
||||
'Base64 is an encoding scheme that represents data in an ASCII string format by translating it into a radix-64 representation. Although it can be used to encode strings, it is commonly used to encode binary data for transmission over media that are designed to deal with textual data.'
|
||||
title: t('string.base64.toolInfo.title'),
|
||||
description: t('string.base64.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
|
|
@ -9,5 +9,10 @@ export const tool = defineTool('string', {
|
|||
'A simple tool to encode or decode data using Base64, which is commonly used in web applications.',
|
||||
shortDescription: 'Encode or decode data using Base64.',
|
||||
keywords: ['base64'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.base64.name',
|
||||
description: 'string.base64.description',
|
||||
shortDescription: 'string.base64.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,5 +12,11 @@ export const tool = defineTool('string', {
|
|||
longDescription:
|
||||
'With this online tool, you can censor certain words in any text. You can specify a list of unwanted words (such as swear words or secret words) and the program will replace them with alternative words and create a safe-to-read text. The words can be specified in a multi-line text field in the options by entering one word per line.',
|
||||
keywords: ['text', 'censor', 'words', 'characters'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.censor.name',
|
||||
description: 'string.censor.description',
|
||||
shortDescription: 'string.censor.shortDescription',
|
||||
longDescription: 'string.censor.longDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,5 +12,10 @@ export const tool = defineTool('string', {
|
|||
longDescription:
|
||||
'This tool creates a palindrome from the given string. It does it by generating a copy of the string, reversing it, and appending it at the end of the original string. This method creates a palindrome with the last character duplicated twice. There is also another way to do it, which deletes the first letter of the reversed copy. In this case, when the string and the copy are joined together, you also get a palindrome but without the repeating last character. You can compare the two types of palindromes by switching between them in the options. You can also enable the multi-line mode that will create palindromes of every string on every line. Stringabulous!',
|
||||
keywords: ['create', 'palindrome'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.createPalindrome.name',
|
||||
description: 'string.createPalindrome.description',
|
||||
shortDescription: 'string.createPalindrome.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,8 +7,13 @@ export const tool = defineTool('string', {
|
|||
path: 'extract-substring',
|
||||
icon: 'material-symbols-light:content-cut',
|
||||
description:
|
||||
"World's simplest browser-based utility for extracting substrings from text. Easily extract specific portions of text by specifying start position and length. Perfect for parsing data, isolating specific parts of text, or data extraction tasks. Supports multi-line text processing and character-level precision.",
|
||||
shortDescription: 'Extract specific portions of text by position and length',
|
||||
"World's simplest browser-based utility for extracting substrings from text. Input your text and specify start and end positions to extract the desired portion. Perfect for data processing, text analysis, or extracting specific content from larger text blocks.",
|
||||
shortDescription: 'Extract a portion of text between specified positions',
|
||||
keywords: ['extract', 'substring'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.extractSubstring.name',
|
||||
description: 'string.extractSubstring.description',
|
||||
shortDescription: 'string.extractSubstring.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
196
src/pages/tools/string/i18n/en.json
Normal file
196
src/pages/tools/string/i18n/en.json
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
{
|
||||
"uppercase": {
|
||||
"title": "Convert to Uppercase",
|
||||
"description": "Convert text to uppercase letters.",
|
||||
"inputTitle": "Input text",
|
||||
"resultTitle": "Uppercase text"
|
||||
},
|
||||
"reverse": {
|
||||
"title": "Reverse Text",
|
||||
"description": "Reverse the order of characters in text.",
|
||||
"inputTitle": "Input text",
|
||||
"resultTitle": "Reversed text",
|
||||
"processMultiLine": "Process as multi-line text (reverse each line separately)",
|
||||
"skipEmptyLines": "Skip empty lines",
|
||||
"trimWhitespace": "Trim whitespace from lines"
|
||||
},
|
||||
"base64": {
|
||||
"title": "Base64 Encoder/Decoder",
|
||||
"description": "Encode or decode text using Base64 encoding.",
|
||||
"inputTitle": "Input Data",
|
||||
"resultTitle": "Result",
|
||||
"optionsTitle": "Base64 Options",
|
||||
"encode": "Base64 Encode",
|
||||
"decode": "Base64 Decode",
|
||||
"toolInfo": {
|
||||
"title": "What is Base64?",
|
||||
"description": "Base64 is an encoding scheme that represents data in an ASCII string format by translating it into a radix-64 representation. Although it can be used to encode strings, it is commonly used to encode binary data for transmission over media that are designed to deal with textual data."
|
||||
}
|
||||
},
|
||||
"truncate": {
|
||||
"title": "Truncate Text",
|
||||
"description": "Shorten text to a specified length.",
|
||||
"inputTitle": "Input text",
|
||||
"resultTitle": "Truncated text",
|
||||
"truncationSide": "Truncation Side",
|
||||
"rightSideTruncation": "Right-side Truncation",
|
||||
"rightSideDescription": "Remove characters from the end of the text.",
|
||||
"leftSideTruncation": "Left-side Truncation",
|
||||
"leftSideDescription": "Remove characters from the start of the text.",
|
||||
"lengthAndLines": "Length and Lines",
|
||||
"maxLengthDescription": "Number of characters to leave in the text.",
|
||||
"numberPlaceholder": "Number",
|
||||
"lineByLineTruncating": "Line-by-line Truncating",
|
||||
"lineByLineDescription": "Truncate each line separately.",
|
||||
"suffixAndAffix": "Suffix and Affix",
|
||||
"addTruncationIndicator": "Add Truncation Indicator",
|
||||
"indicatorDescription": "Characters to add at the end (or start) of the text. Note: They count towards the length.",
|
||||
"charactersPlaceholder": "Characters",
|
||||
"toolInfo": {
|
||||
"title": "Truncate text",
|
||||
"description": "Load your text in the input form on the left and you will automatically get truncated text on the right."
|
||||
}
|
||||
},
|
||||
"quote": {
|
||||
"title": "Text Quoter",
|
||||
"description": "Add quotes around text with customizable options.",
|
||||
"inputTitle": "Input Text",
|
||||
"resultTitle": "Quoted Text",
|
||||
"quoteOptions": "Quote Options",
|
||||
"leftQuoteDescription": "Left quote character(s)",
|
||||
"rightQuoteDescription": "Right quote character(s)",
|
||||
"allowDoubleQuotation": "Allow double quotation",
|
||||
"quoteEmptyLines": "Quote empty lines",
|
||||
"processAsMultiLine": "Process as multi-line text",
|
||||
"toolInfo": {
|
||||
"title": "Text Quoter",
|
||||
"description": "This tool allows you to add quotes around text. You can choose different quote characters, handle multi-line text, and control how empty lines are processed. It's useful for preparing text for programming, formatting data, or creating stylized text."
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"title": "Split Text",
|
||||
"description": "Split text into parts based on various criteria.",
|
||||
"resultTitle": "Text pieces",
|
||||
"splitSeparatorOptions": "Split separator options",
|
||||
"symbolTitle": "Use a Symbol for Splitting",
|
||||
"symbolDescription": "Character that will be used to break text into parts. (Space by default.)",
|
||||
"regexTitle": "Use a Regex for Splitting",
|
||||
"regexDescription": "Regular expression that will be used to break text into parts. (Multiple spaces by default.)",
|
||||
"lengthTitle": "Use Length for Splitting",
|
||||
"lengthDescription": "Number of symbols that will be put in each output chunk.",
|
||||
"chunksTitle": "Use a Number of Chunks",
|
||||
"chunksDescription": "Number of chunks of equal length in the output.",
|
||||
"outputSeparatorOptions": "Output separator options",
|
||||
"outputSeparatorDescription": "Character that will be put between the split chunks. (It's newline \"\\n\" by default.)",
|
||||
"charBeforeChunkDescription": "Character before each chunk",
|
||||
"charAfterChunkDescription": "Character after each chunk"
|
||||
},
|
||||
"join": {
|
||||
"title": "Join Text",
|
||||
"description": "Join text pieces together with customizable separators.",
|
||||
"inputTitle": "Text Pieces",
|
||||
"resultTitle": "Joined Text",
|
||||
"textMergedOptions": "Text Merged Options",
|
||||
"joinCharacterPlaceholder": "Join Character",
|
||||
"joinCharacterDescription": "Symbol that connects broken pieces of text. (Space by default.)",
|
||||
"blankLinesAndTrailingSpaces": "Blank Lines and Trailing Spaces",
|
||||
"deleteBlankTitle": "Delete Blank Lines",
|
||||
"deleteBlankDescription": "Delete lines that don't have text symbols.",
|
||||
"deleteTrailingTitle": "Delete Trailing Spaces",
|
||||
"deleteTrailingDescription": "Remove spaces and tabs at the end of the lines.",
|
||||
"toolInfo": {
|
||||
"title": "What Is a Text Joiner?",
|
||||
"description": "With this tool you can join parts of the text together. It takes a list of text values, separated by newlines, and merges them together. You can set the character that will be placed between the parts of the combined text. Also, you can ignore all empty lines and remove spaces and tabs at the end of all lines. Textabulous!"
|
||||
}
|
||||
},
|
||||
"rotate": {
|
||||
"title": "Rotate Text",
|
||||
"description": "Rotate characters in text by specified positions.",
|
||||
"inputTitle": "Input Text",
|
||||
"resultTitle": "Rotated Text",
|
||||
"rotationOptions": "Rotation Options",
|
||||
"stepDescription": "Number of positions to rotate",
|
||||
"rotateRight": "Rotate Right",
|
||||
"rotateLeft": "Rotate Left",
|
||||
"processAsMultiLine": "Process as multi-line text (rotate each line separately)",
|
||||
"toolInfo": {
|
||||
"title": "String Rotation",
|
||||
"description": "This tool allows you to rotate characters in a string by a specified number of positions. You can rotate to the left or right, and process multi-line text by rotating each line separately. String rotation is useful for simple text transformations, creating patterns, or implementing basic encryption techniques."
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"title": "Repeat Text",
|
||||
"description": "Repeat text multiple times with customizable separators.",
|
||||
"inputTitle": "Input text",
|
||||
"resultTitle": "Repeated text",
|
||||
"textRepetitions": "Text Repetitions",
|
||||
"repeatAmountDescription": "Number of repetitions.",
|
||||
"numberPlaceholder": "Number",
|
||||
"repetitionsDelimiter": "Repetitions Delimiter",
|
||||
"delimiterDescription": "Delimiter for output copies.",
|
||||
"delimiterPlaceholder": "Delimiter",
|
||||
"toolInfo": {
|
||||
"title": "Repeat text",
|
||||
"description": "This tool allows you to repeat a given text multiple times with an optional separator."
|
||||
}
|
||||
},
|
||||
"rot13": {
|
||||
"title": "ROT13 Encoder/Decoder",
|
||||
"description": "Encode or decode text using ROT13 cipher.",
|
||||
"inputTitle": "Input Text",
|
||||
"resultTitle": "ROT13 Result",
|
||||
"toolInfo": {
|
||||
"title": "What Is ROT13?",
|
||||
"description": "ROT13 (rotate by 13 places) is a simple letter substitution cipher that replaces a letter with the 13th letter after it in the alphabet. ROT13 is a special case of the Caesar cipher which was developed in ancient Rome. Because there are 26 letters in the English alphabet, ROT13 is its own inverse; that is, to undo ROT13, the same algorithm is applied, so the same action can be used for encoding and decoding."
|
||||
}
|
||||
},
|
||||
"toMorse": {
|
||||
"title": "To Morse",
|
||||
"description": "Convert text to Morse code.",
|
||||
"resultTitle": "Morse code",
|
||||
"shortSignal": "Short Signal",
|
||||
"dotSymbolDescription": "Symbol that will correspond to the dot in Morse code.",
|
||||
"longSignal": "Long Signal",
|
||||
"dashSymbolDescription": "Symbol that will correspond to the dash in Morse code."
|
||||
},
|
||||
"statistic": {
|
||||
"title": "Text Statistics",
|
||||
"description": "Analyze text and generate comprehensive statistics.",
|
||||
"inputTitle": "Input text",
|
||||
"resultTitle": "Text Statistics",
|
||||
"delimitersOptions": "Delimiters Options",
|
||||
"sentenceDelimitersPlaceholder": "e.g. ., !, ?, ...",
|
||||
"sentenceDelimitersDescription": "Enter custom characters used to delimit sentences in your language (separated by comma) or leave it blank for default.",
|
||||
"wordDelimitersPlaceholder": "eg. \\s.,;:!?\"«»()…",
|
||||
"wordDelimitersDescription": "Enter custom Regex to count Words or leave it blank for default.",
|
||||
"statisticsOptions": "Statistics Options",
|
||||
"wordFrequencyAnalysis": "Word Frequency Analysis",
|
||||
"wordFrequencyAnalysisDescription": "Count how often each word appears in the text",
|
||||
"characterFrequencyAnalysis": "Character Frequency Analysis",
|
||||
"characterFrequencyAnalysisDescription": "Count how often each character appears in the text",
|
||||
"includeEmptyLines": "Include Empty Lines",
|
||||
"includeEmptyLinesDescription": "Include blank lines when counting lines",
|
||||
"toolInfo": {
|
||||
"title": "What is a {{title}}?",
|
||||
"description": "This tool allows you to analyze text and generate comprehensive statistics including character count, word count, line count, and frequency analysis of characters and words."
|
||||
}
|
||||
},
|
||||
"textReplacer": {
|
||||
"title": "Text Replacer",
|
||||
"description": "Replace text patterns with new content.",
|
||||
"inputTitle": "Text to replace",
|
||||
"resultTitle": "Text with replacements",
|
||||
"searchText": "Search text",
|
||||
"findPatternInText": "Find This Pattern in Text",
|
||||
"searchPatternDescription": "Enter the text pattern that you want to replace.",
|
||||
"findPatternUsingRegexp": "Find a Pattern Using a RegExp",
|
||||
"regexpDescription": "Enter the regular expression that you want to replace.",
|
||||
"replaceText": "Replace Text",
|
||||
"replacePatternDescription": "Enter the pattern to use for replacement.",
|
||||
"newTextPlaceholder": "New text",
|
||||
"toolInfo": {
|
||||
"title": "Text Replacer",
|
||||
"description": "Easily replace specific text in your content with this simple, browser-based tool. Just input your text, set the text you want to replace and the replacement value, and instantly get the updated version."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
|||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
joinCharacter: '',
|
||||
|
|
@ -107,6 +108,7 @@ s
|
|||
];
|
||||
|
||||
export default function JoinText({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
const compute = (optionsValues: InitialValuesType, input: any) => {
|
||||
|
|
@ -119,25 +121,25 @@ export default function JoinText({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Text Merged Options',
|
||||
title: t('string.join.textMergedOptions'),
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
placeholder={mergeOptions.placeholder}
|
||||
placeholder={t('string.join.joinCharacterPlaceholder')}
|
||||
value={values['joinCharacter']}
|
||||
onOwnChange={(value) => updateField(mergeOptions.accessor, value)}
|
||||
description={mergeOptions.description}
|
||||
description={t('string.join.joinCharacterDescription')}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Blank Lines and Trailing Spaces',
|
||||
title: t('string.join.blankLinesAndTrailingSpaces'),
|
||||
component: blankTrailingOptions.map((option) => (
|
||||
<CheckboxWithDesc
|
||||
key={option.accessor}
|
||||
title={option.title}
|
||||
title={t(`string.join.${option.accessor}Title`)}
|
||||
checked={!!values[option.accessor]}
|
||||
onChange={(value) => updateField(option.accessor, value)}
|
||||
description={option.description}
|
||||
description={t(`string.join.${option.accessor}Description`)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
|
@ -151,17 +153,18 @@ export default function JoinText({ title }: ToolComponentProps) {
|
|||
setInput={setInput}
|
||||
inputComponent={
|
||||
<ToolTextInput
|
||||
title={'Text Pieces'}
|
||||
title={t('string.join.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={<ToolTextResult title={'Joined Text'} value={result} />}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('string.join.resultTitle')} value={result} />
|
||||
}
|
||||
getGroups={getGroups}
|
||||
toolInfo={{
|
||||
title: 'What Is a Text Joiner?',
|
||||
description:
|
||||
'With this tool you can join parts of the text together. It takes a list of text values, separated by newlines, and merges them together. You can set the character that will be placed between the parts of the combined text. Also, you can ignore all empty lines and remove spaces and tabs at the end of all lines. Textabulous!'
|
||||
title: t('string.join.toolInfo.title'),
|
||||
description: t('string.join.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@ import { defineTool } from '@tools/defineTool';
|
|||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'Join',
|
||||
path: 'join',
|
||||
name: 'Text Joiner',
|
||||
icon: 'tabler:arrows-join',
|
||||
icon: 'material-symbols-light:join',
|
||||
description:
|
||||
"World's Simplest Text Tool World's simplest browser-based utility for joining text. Load your text in the input form on the left and you'll automatically get merged text on the right. Powerful, free, and fast. Load text – get joined lines",
|
||||
shortDescription: 'Quickly merge texts',
|
||||
keywords: ['text', 'join'],
|
||||
component: lazy(() => import('./index'))
|
||||
"World's simplest browser-based utility for joining text elements. Input your text elements and specify a separator to combine them into a single string. Perfect for data processing, text manipulation, or creating formatted output from lists.",
|
||||
shortDescription: 'Join text elements with a specified separator',
|
||||
keywords: ['join'],
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.join.name',
|
||||
description: 'string.join.description',
|
||||
shortDescription: 'string.join.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,5 +10,10 @@ export const tool = defineTool('string', {
|
|||
"World's simplest browser-based utility for checking if text is a palindrome. Instantly verify if your text reads the same forward and backward. Perfect for word puzzles, linguistic analysis, or validating symmetrical text patterns. Supports various delimiters and multi-word palindrome detection.",
|
||||
shortDescription: 'Check if text reads the same forward and backward',
|
||||
keywords: ['palindrome'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.palindrome.name',
|
||||
description: 'string.palindrome.description',
|
||||
shortDescription: 'string.palindrome.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ToolComponentProps } from '@tools/defineTool';
|
|||
import { GetGroupsType } from '@components/options/ToolOptions';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface InitialValuesType {
|
||||
leftQuote: string;
|
||||
|
|
@ -70,6 +71,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function Quote({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -93,33 +95,33 @@ export default function Quote({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Quote Options',
|
||||
title: t('string.quote.quoteOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.leftQuote}
|
||||
onOwnChange={(val) => updateField('leftQuote', val)}
|
||||
description={'Left quote character(s)'}
|
||||
description={t('string.quote.leftQuoteDescription')}
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
value={values.rightQuote}
|
||||
onOwnChange={(val) => updateField('rightQuote', val)}
|
||||
description={'Right quote character(s)'}
|
||||
description={t('string.quote.rightQuoteDescription')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.doubleQuotation}
|
||||
onChange={(checked) => updateField('doubleQuotation', checked)}
|
||||
title={'Allow double quotation'}
|
||||
title={t('string.quote.allowDoubleQuotation')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.emptyQuoting}
|
||||
onChange={(checked) => updateField('emptyQuoting', checked)}
|
||||
title={'Quote empty lines'}
|
||||
title={t('string.quote.quoteEmptyLines')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.multiLine}
|
||||
onChange={(checked) => updateField('multiLine', checked)}
|
||||
title={'Process as multi-line text'}
|
||||
title={t('string.quote.processAsMultiLine')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -130,15 +132,20 @@ export default function Quote({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input Text" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('string.quote.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('string.quote.resultTitle')} value={result} />
|
||||
}
|
||||
resultComponent={<ToolTextResult title="Quoted Text" value={result} />}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
toolInfo={{
|
||||
title: 'Text Quoter',
|
||||
description:
|
||||
"This tool allows you to add quotes around text. You can choose different quote characters, handle multi-line text, and control how empty lines are processed. It's useful for preparing text for programming, formatting data, or creating stylized text."
|
||||
title: t('string.quote.toolInfo.title'),
|
||||
description: t('string.quote.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('string', {
|
||||
name: 'Quote',
|
||||
path: 'quote',
|
||||
icon: 'proicons:quote',
|
||||
icon: 'material-symbols-light:format-quote',
|
||||
description:
|
||||
'A tool to add quotation marks or custom characters around text. Perfect for formatting strings for code, citations, or stylistic purposes.',
|
||||
shortDescription: 'Add quotes around text easily.',
|
||||
"World's simplest browser-based utility for adding quotes to text. Input your text and instantly add various quote styles around it. Perfect for formatting text, creating citations, or adding emphasis to specific content.",
|
||||
shortDescription: 'Add quotes around text with various styles',
|
||||
keywords: ['quote'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.quote.name',
|
||||
description: 'string.quote.description',
|
||||
shortDescription: 'string.quote.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import { lazy } from 'react';
|
|||
export const tool = defineTool('string', {
|
||||
name: 'Randomize case',
|
||||
path: 'randomize-case',
|
||||
icon: 'material-symbols-light:format-textdirection-l-to-r',
|
||||
icon: 'material-symbols-light:shuffle',
|
||||
description:
|
||||
"World's simplest browser-based utility for randomizing the case of text. Just paste your text and get it instantly transformed with random uppercase and lowercase letters. Perfect for creating playful text styles, meme text, or simulating chaotic writing.",
|
||||
shortDescription: 'Convert text to random uppercase and lowercase letters',
|
||||
"World's simplest browser-based utility for randomizing text case. Input your text and instantly transform it with random upper and lower case letters. Perfect for creating unique text effects, testing case sensitivity, or generating varied text patterns.",
|
||||
shortDescription: 'Randomize the case of letters in text',
|
||||
keywords: ['randomize', 'case'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.randomizeCase.name',
|
||||
description: 'string.randomizeCase.description',
|
||||
shortDescription: 'string.randomizeCase.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,5 +9,10 @@ export const tool = defineTool('string', {
|
|||
"Load your text in the input form on the left and you'll instantly get text with no duplicate lines in the output area. Powerful, free, and fast. Load text lines – get unique text lines",
|
||||
shortDescription: 'Quickly delete all repeated lines from text',
|
||||
keywords: ['remove', 'duplicate', 'lines'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.removeDuplicateLines.name',
|
||||
description: 'string.removeDuplicateLines.description',
|
||||
shortDescription: 'string.removeDuplicateLines.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { initialValues, InitialValuesType } from './initialValues';
|
|||
import ToolContent from '@components/ToolContent';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
{
|
||||
|
|
@ -48,6 +49,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function Replacer({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -60,12 +62,12 @@ export default function Replacer({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Text Repetitions',
|
||||
title: t('string.repeat.textRepetitions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description={'Number of repetitions.'}
|
||||
placeholder="Number"
|
||||
description={t('string.repeat.repeatAmountDescription')}
|
||||
placeholder={t('string.repeat.numberPlaceholder')}
|
||||
value={values.repeatAmount}
|
||||
onOwnChange={(val) => updateField('repeatAmount', val)}
|
||||
type={'number'}
|
||||
|
|
@ -74,12 +76,12 @@ export default function Replacer({ title }: ToolComponentProps) {
|
|||
)
|
||||
},
|
||||
{
|
||||
title: 'Repetitions Delimiter',
|
||||
title: t('string.repeat.repetitionsDelimiter'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description={'Delimiter for output copies.'}
|
||||
placeholder="Delimiter"
|
||||
description={t('string.repeat.delimiterDescription')}
|
||||
placeholder={t('string.repeat.delimiterPlaceholder')}
|
||||
value={values.delimiter}
|
||||
onOwnChange={(val) => updateField('delimiter', val)}
|
||||
type={'text'}
|
||||
|
|
@ -98,15 +100,18 @@ export default function Replacer({ title }: ToolComponentProps) {
|
|||
input={input}
|
||||
setInput={setInput}
|
||||
inputComponent={
|
||||
<ToolTextInput title={'Input text'} value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('string.repeat.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={'Repeated text'} value={result} />
|
||||
<ToolTextResult title={t('string.repeat.resultTitle')} value={result} />
|
||||
}
|
||||
toolInfo={{
|
||||
title: 'Repeat text',
|
||||
description:
|
||||
'This tool allows you to repeat a given text multiple times with an optional separator.'
|
||||
title: t('string.repeat.toolInfo.title'),
|
||||
description: t('string.repeat.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,5 +9,10 @@ export const tool = defineTool('string', {
|
|||
description:
|
||||
'This tool allows you to repeat a given text multiple times with an optional separator.',
|
||||
keywords: ['text', 'repeat'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.repeat.name',
|
||||
description: 'string.repeat.description',
|
||||
shortDescription: 'string.repeat.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import ToolExamples, {
|
|||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { FormikProps } from 'formik';
|
||||
import ToolContent from '@components/ToolContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const initialValues = {
|
||||
multiLine: true,
|
||||
|
|
@ -58,6 +59,7 @@ const exampleCards: CardExampleType<typeof initialValues>[] = [
|
|||
];
|
||||
|
||||
export default function Reverse({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -74,27 +76,27 @@ export default function Reverse({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Reversal options',
|
||||
title: t('string.reverse.reversalOptions'),
|
||||
component: [
|
||||
<CheckboxWithDesc
|
||||
key="multiLine"
|
||||
checked={values.multiLine}
|
||||
title="Process multi-line text"
|
||||
description="Each line will be reversed independently"
|
||||
title={t('string.reverse.processMultiLine')}
|
||||
description={t('string.reverse.processMultiLineDescription')}
|
||||
onChange={(val) => updateField('multiLine', val)}
|
||||
/>,
|
||||
<CheckboxWithDesc
|
||||
key="emptyItems"
|
||||
checked={values.emptyItems}
|
||||
title="Skip empty lines"
|
||||
description="Empty lines will be removed from the output"
|
||||
title={t('string.reverse.skipEmptyLines')}
|
||||
description={t('string.reverse.skipEmptyLinesDescription')}
|
||||
onChange={(val) => updateField('emptyItems', val)}
|
||||
/>,
|
||||
<CheckboxWithDesc
|
||||
key="trim"
|
||||
checked={values.trim}
|
||||
title="Trim whitespace"
|
||||
description="Remove leading and trailing whitespace from each line"
|
||||
title={t('string.reverse.trimWhitespace')}
|
||||
description={t('string.reverse.trimWhitespaceDescription')}
|
||||
onChange={(val) => updateField('trim', val)}
|
||||
/>
|
||||
]
|
||||
|
|
@ -109,9 +111,18 @@ export default function Reverse({ title }: ToolComponentProps) {
|
|||
compute={computeExternal}
|
||||
input={input}
|
||||
setInput={setInput}
|
||||
inputComponent={<ToolTextInput value={input} onChange={setInput} />}
|
||||
inputComponent={
|
||||
<ToolTextInput
|
||||
title={t('string.reverse.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={'Reversed text'} value={result} />
|
||||
<ToolTextResult
|
||||
title={t('string.reverse.resultTitle')}
|
||||
value={result}
|
||||
/>
|
||||
}
|
||||
exampleCards={exampleCards}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -9,5 +9,10 @@ export const tool = defineTool('string', {
|
|||
"World's simplest browser-based utility for reversing text. Input any text and get it instantly reversed, character by character. Perfect for creating mirror text, analyzing palindromes, or playing with text patterns. Preserves spaces and special characters while reversing.",
|
||||
shortDescription: 'Reverse any text character by character',
|
||||
keywords: ['reverse'],
|
||||
component: lazy(() => import('./index'))
|
||||
component: lazy(() => import('./index')),
|
||||
i18n: {
|
||||
name: 'string.reverse.name',
|
||||
description: 'string.reverse.description',
|
||||
shortDescription: 'string.reverse.shortDescription'
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import ToolTextResult from '@components/result/ToolTextResult';
|
|||
import { rot13 } from './service';
|
||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type InitialValuesType = Record<string, never>;
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function Rot13({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -41,15 +43,20 @@ export default function Rot13({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input Text" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('string.rot13.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('string.rot13.resultTitle')} value={result} />
|
||||
}
|
||||
resultComponent={<ToolTextResult title="ROT13 Result" value={result} />}
|
||||
initialValues={initialValues}
|
||||
getGroups={null}
|
||||
toolInfo={{
|
||||
title: 'What Is ROT13?',
|
||||
description:
|
||||
'ROT13 (rotate by 13 places) is a simple letter substitution cipher that replaces a letter with the 13th letter after it in the alphabet. ROT13 is a special case of the Caesar cipher which was developed in ancient Rome. Because there are 26 letters in the English alphabet, ROT13 is its own inverse; that is, to undo ROT13, the same algorithm is applied, so the same action can be used for encoding and decoding.'
|
||||
title: t('string.rot13.toolInfo.title'),
|
||||
description: t('string.rot13.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { GetGroupsType } from '@components/options/ToolOptions';
|
|||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface InitialValuesType {
|
||||
step: string;
|
||||
|
|
@ -63,6 +64,7 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||
];
|
||||
|
||||
export default function Rotate({ title }: ToolComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
|
|
@ -79,29 +81,29 @@ export default function Rotate({ title }: ToolComponentProps) {
|
|||
updateField
|
||||
}) => [
|
||||
{
|
||||
title: 'Rotation Options',
|
||||
title: t('string.rotate.rotationOptions'),
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
value={values.step}
|
||||
onOwnChange={(val) => updateField('step', val)}
|
||||
description={'Number of positions to rotate'}
|
||||
description={t('string.rotate.stepDescription')}
|
||||
type="number"
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('direction', 'right')}
|
||||
checked={values.direction === 'right'}
|
||||
title={'Rotate Right'}
|
||||
title={t('string.rotate.rotateRight')}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('direction', 'left')}
|
||||
checked={values.direction === 'left'}
|
||||
title={'Rotate Left'}
|
||||
title={t('string.rotate.rotateLeft')}
|
||||
/>
|
||||
<CheckboxWithDesc
|
||||
checked={values.multiLine}
|
||||
onChange={(checked) => updateField('multiLine', checked)}
|
||||
title={'Process as multi-line text (rotate each line separately)'}
|
||||
title={t('string.rotate.processAsMultiLine')}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
|
|
@ -112,15 +114,20 @@ export default function Rotate({ title }: ToolComponentProps) {
|
|||
<ToolContent
|
||||
title={title}
|
||||
inputComponent={
|
||||
<ToolTextInput title="Input Text" value={input} onChange={setInput} />
|
||||
<ToolTextInput
|
||||
title={t('string.rotate.inputTitle')}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolTextResult title={t('string.rotate.resultTitle')} value={result} />
|
||||
}
|
||||
resultComponent={<ToolTextResult title="Rotated Text" value={result} />}
|
||||
initialValues={initialValues}
|
||||
getGroups={getGroups}
|
||||
toolInfo={{
|
||||
title: 'String Rotation',
|
||||
description:
|
||||
'This tool allows you to rotate characters in a string by a specified number of positions. You can rotate to the left or right, and process multi-line text by rotating each line separately. String rotation is useful for simple text transformations, creating patterns, or implementing basic encryption techniques.'
|
||||
title: t('string.rotate.toolInfo.title'),
|
||||
description: t('string.rotate.toolInfo.description')
|
||||
}}
|
||||
exampleCards={exampleCards}
|
||||
input={input}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue