mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-12-29 16:16:02 +00:00
feat: add internationalization support
This commit is contained in:
@@ -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>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user