fix: missing translations

This commit is contained in:
Ibrahima G. Coulibaly
2025-07-16 15:58:41 +01:00
parent e533c6e39c
commit 2a05f42d1d
14 changed files with 232 additions and 104 deletions

View File

@@ -1,5 +1,6 @@
import { Box, FormControlLabel, Radio, RadioGroup } from '@mui/material';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
@@ -24,6 +25,7 @@ export default function MergeAudio({
title,
longDescription
}: ToolComponentProps) {
const { t } = useTranslation('audio');
const [input, setInput] = useState<MultiAudioInput[]>([]);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -51,7 +53,7 @@ export default function MergeAudio({
updateField
}) => [
{
title: 'Output Format',
title: t('mergeAudio.outputFormat'),
component: (
<Box mt={2}>
<RadioGroup
@@ -87,16 +89,20 @@ export default function MergeAudio({
value={input}
onChange={setInput}
accept={['audio/*', '.mp3', '.wav', '.aac']}
title={'Input Audio Files'}
title={t('mergeAudio.inputTitle')}
type="audio"
/>
}
resultComponent={
loading ? (
<ToolFileResult title="Merging Audio" value={null} loading={true} />
<ToolFileResult
title={t('mergeAudio.mergingAudio')}
value={null}
loading={true}
/>
) : (
<ToolFileResult
title="Merged Audio"
title={t('mergeAudio.resultTitle')}
value={result}
extension={result ? result.name.split('.').pop() : undefined}
/>
@@ -106,7 +112,10 @@ export default function MergeAudio({
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is ${title}?`, description: longDescription }}
toolInfo={{
title: t('mergeAudio.toolInfo.title', { title }),
description: longDescription
}}
/>
);
}

View File

@@ -1,5 +1,6 @@
import { Box, FormControlLabel, Radio, RadioGroup } from '@mui/material';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
@@ -22,6 +23,7 @@ const formatOptions = [
];
export default function Trim({ title, longDescription }: ToolComponentProps) {
const { t } = useTranslation('audio');
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
@@ -48,28 +50,28 @@ export default function Trim({ title, longDescription }: ToolComponentProps) {
updateField
}) => [
{
title: 'Time Settings',
title: t('trim.timeSettings'),
component: (
<Box>
<TextFieldWithDesc
value={values.startTime}
onOwnChange={(val) => updateField('startTime', val)}
description="Start time in format HH:MM:SS (e.g., 00:00:30)"
label="Start Time"
description={t('trim.startTimeDescription')}
label={t('trim.startTime')}
/>
<Box mt={2}>
<TextFieldWithDesc
value={values.endTime}
onOwnChange={(val) => updateField('endTime', val)}
description="End time in format HH:MM:SS (e.g., 00:01:30)"
label="End Time"
description={t('trim.endTimeDescription')}
label={t('trim.endTime')}
/>
</Box>
</Box>
)
},
{
title: 'Output Format',
title: t('trim.outputFormat'),
component: (
<Box mt={2}>
<RadioGroup
@@ -104,15 +106,19 @@ export default function Trim({ title, longDescription }: ToolComponentProps) {
<ToolAudioInput
value={input}
onChange={setInput}
title={'Input Audio'}
title={t('trim.inputTitle')}
/>
}
resultComponent={
loading ? (
<ToolFileResult title="Trimming Audio" value={null} loading={true} />
<ToolFileResult
title={t('trim.trimmingAudio')}
value={null}
loading={true}
/>
) : (
<ToolFileResult
title="Trimmed Audio"
title={t('trim.resultTitle')}
value={result}
extension={result ? result.name.split('.').pop() : undefined}
/>
@@ -122,7 +128,10 @@ export default function Trim({ title, longDescription }: ToolComponentProps) {
getGroups={getGroups}
setInput={setInput}
compute={compute}
toolInfo={{ title: `What is ${title}?`, description: longDescription }}
toolInfo={{
title: t('trim.toolInfo.title', { title }),
description: longDescription
}}
/>
);
}

View File

@@ -131,8 +131,10 @@ export default function CsvToJson({ title }: ToolComponentProps) {
setResult(jsonResult);
} catch (error) {
setResult(
`Error: ${
error instanceof Error ? error.message : 'Invalid CSV format'
`${t('csvToJson.error')}: ${
error instanceof Error
? error.message
: t('csvToJson.invalidCsvFormat')
}`
);
}
@@ -211,9 +213,8 @@ export default function CsvToJson({ title }: ToolComponentProps) {
}
]}
toolInfo={{
title: 'What Is a CSV to JSON Converter?',
description:
'This tool transforms Comma Separated Values (CSV) files to JavaScript Object Notation (JSON) data structures. It supports various CSV formats with customizable delimiters, quote characters, and comment symbols. The converter can treat the first row as headers, skip empty lines, and automatically detect data types like numbers and booleans. The resulting JSON can be used for data migration, backups, or as input for other applications.'
title: t('csvToJson.toolInfo.title'),
description: t('csvToJson.toolInfo.description')
}}
/>
);

View File

@@ -1,4 +1,5 @@
import React, { useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { InitialValuesType } from './types';
import { compressImage } from './service';
import ToolContent from '@components/ToolContent';
@@ -17,6 +18,7 @@ const initialValues: InitialValuesType = {
};
export default function CompressImage({ title }: ToolComponentProps) {
const { t } = useTranslation('image');
const [input, setInput] = useState<File | null>(null);
const [result, setResult] = useState<File | null>(null);
const [isProcessing, setIsProcessing] = useState<boolean>(false);
@@ -37,7 +39,7 @@ export default function CompressImage({ title }: ToolComponentProps) {
setResult(compressed);
setCompressedSize(compressed.size);
} else {
showSnackBar('Failed to compress image. Please try again.', 'error');
showSnackBar(t('compress.failedToCompress'), 'error');
}
} catch (err) {
console.error('Error in compression:', err);
@@ -55,12 +57,12 @@ export default function CompressImage({ title }: ToolComponentProps) {
value={input}
onChange={setInput}
accept={['image/*']}
title={'Input image'}
title={t('compress.inputTitle')}
/>
}
resultComponent={
<ToolFileResult
title={'Compressed image'}
title={t('compress.resultTitle')}
value={result}
loading={isProcessing}
/>
@@ -68,14 +70,14 @@ export default function CompressImage({ title }: ToolComponentProps) {
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'Compression options',
title: t('compress.compressionOptions'),
component: (
<Box>
<TextFieldWithDesc
name="maxFileSizeInMB"
type="number"
inputProps={{ min: 0.1, step: 0.1 }}
description="Maximum file size in megabytes"
description={t('compress.maxFileSizeDescription')}
onOwnChange={(value) =>
updateNumberField(value, 'maxFileSizeInMB', updateField)
}
@@ -85,7 +87,7 @@ export default function CompressImage({ title }: ToolComponentProps) {
name="quality"
type="number"
inputProps={{ min: 10, max: 100, step: 1 }}
description="Image quality percentage (lower means smaller file size)"
description={t('compress.qualityDescription')}
onOwnChange={(value) =>
updateNumberField(value, 'quality', updateField)
}
@@ -95,18 +97,20 @@ export default function CompressImage({ title }: ToolComponentProps) {
)
},
{
title: 'File sizes',
title: t('compress.fileSizes'),
component: (
<Box>
<Box>
{originalSize !== null && (
<Typography>
Original Size: {(originalSize / 1024).toFixed(2)} KB
{t('compress.originalSize')}:{' '}
{(originalSize / 1024).toFixed(2)} KB
</Typography>
)}
{compressedSize !== null && (
<Typography>
Compressed Size: {(compressedSize / 1024).toFixed(2)} KB
{t('compress.compressedSize')}:{' '}
{(compressedSize / 1024).toFixed(2)} KB
</Typography>
)}
</Box>

View File

@@ -123,9 +123,9 @@ export default function Duplicate({ title }: ToolComponentProps) {
);
} catch (error) {
if (error instanceof Error) {
setResult(`Error: ${error.message}`);
setResult(`${t('duplicate.error')}: ${error.message}`);
} else {
setResult('An unknown error occurred');
setResult(t('duplicate.unknownError'));
}
}
}

View File

@@ -1,5 +1,6 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import {
@@ -14,6 +15,7 @@ import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import SelectWithDesc from '@components/options/SelectWithDesc';
import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool';
import { ParseKeys } from 'i18next';
const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType,
@@ -25,23 +27,24 @@ const initialValues = {
trimItems: false
};
const splitOperators: {
title: string;
description: string;
title: ParseKeys<'list'>;
description: ParseKeys<'list'>;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
title: 'findMostPopular.splitOperators.symbol.title',
description: 'findMostPopular.splitOperators.symbol.description',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
title: 'findMostPopular.splitOperators.regex.title',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
description: 'findMostPopular.splitOperators.regex.description'
}
];
export default function FindMostPopular({ title }: ToolComponentProps) {
const { t } = useTranslation('list');
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
@@ -74,28 +77,35 @@ export default function FindMostPopular({ title }: ToolComponentProps) {
title={title}
input={input}
inputComponent={
<ToolTextInput title={'Input list'} value={input} onChange={setInput} />
<ToolTextInput
title={t('findMostPopular.inputTitle')}
value={input}
onChange={setInput}
/>
}
resultComponent={
<ToolTextResult title={'Most popular items'} value={result} />
<ToolTextResult
title={t('findMostPopular.resultTitle')}
value={result}
/>
}
initialValues={initialValues}
getGroups={({ values, updateField }) => [
{
title: 'How to Extract List Items?',
title: t('findMostPopular.extractListItems'),
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitSeparatorType', type)}
title={title}
description={description}
title={t(title)}
description={t(description)}
checked={values.splitSeparatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
description={t('findMostPopular.splitSeparatorDescription')}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
@@ -103,26 +113,24 @@ export default function FindMostPopular({ title }: ToolComponentProps) {
)
},
{
title: 'Item comparison',
title: t('findMostPopular.itemComparison'),
component: (
<Box>
<CheckboxWithDesc
title={'Remove empty items'}
description={'Ignore empty items from comparison.'}
title={t('findMostPopular.removeEmptyItems')}
description={t('findMostPopular.removeEmptyItemsDescription')}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
<CheckboxWithDesc
title={'Trim top list items'}
description={
'Remove leading and trailing spaces before comparing items'
}
title={t('findMostPopular.trimItems')}
description={t('findMostPopular.trimItemsDescription')}
checked={values.trimItems}
onChange={(value) => updateField('trimItems', value)}
/>
<CheckboxWithDesc
title={'Ignore Item Case'}
description={'Compare all list items in lowercase.'}
title={t('findMostPopular.ignoreItemCase')}
description={t('findMostPopular.ignoreItemCaseDescription')}
checked={values.ignoreItemCase}
onChange={(value) => updateField('ignoreItemCase', value)}
/>
@@ -130,27 +138,42 @@ export default function FindMostPopular({ title }: ToolComponentProps) {
)
},
{
title: 'Top item output format',
title: t('findMostPopular.outputFormat'),
component: (
<Box>
<SelectWithDesc
selected={values.displayFormat}
options={[
{ label: 'Show item percentage', value: 'percentage' },
{ label: 'Show item count', value: 'count' },
{ label: 'Show item total', value: 'total' }
{
label: t('findMostPopular.displayOptions.percentage'),
value: 'percentage'
},
{
label: t('findMostPopular.displayOptions.count'),
value: 'count'
},
{
label: t('findMostPopular.displayOptions.total'),
value: 'total'
}
]}
onChange={(value) => updateField('displayFormat', value)}
description={'How to display the most popular list items?'}
description={t('findMostPopular.displayFormatDescription')}
/>
<SelectWithDesc
selected={values.sortingMethod}
options={[
{ label: 'Sort Alphabetically', value: 'alphabetic' },
{ label: 'Sort by count', value: 'count' }
{
label: t('findMostPopular.sortOptions.alphabetic'),
value: 'alphabetic'
},
{
label: t('findMostPopular.sortOptions.count'),
value: 'count'
}
]}
onChange={(value) => updateField('sortingMethod', value)}
description={'Select a sorting method.'}
description={t('findMostPopular.sortingMethodDescription')}
/>
</Box>
)

View File

@@ -12,6 +12,7 @@ import { ToolComponentProps } from '@tools/defineTool';
import { FormikProps } from 'formik';
import ToolContent from '@components/ToolContent';
import { useTranslation } from 'react-i18next';
import { ParseKeys } from 'i18next';
const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType,
@@ -25,55 +26,45 @@ const initialValues = {
charAfterChunk: ''
};
const splitOperators: {
title: string;
description: string;
title: ParseKeys<'string'>;
description: ParseKeys<'string'>;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description:
'Character that will be used to\n' +
'break text into parts.\n' +
'(Space by default.)',
title: 'split.symbolTitle',
description: 'split.symbolDescription',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
title: 'split.regexTitle',
type: 'regex',
description:
'Regular expression that will be\n' +
'used to break text into parts.\n' +
'(Multiple spaces by default.)'
description: 'split.regexDescription'
},
{
title: 'Use Length for Splitting',
description:
'Number of symbols that will be\n' + 'put in each output chunk.',
title: 'split.lengthTitle',
description: 'split.lengthDescription',
type: 'length'
},
{
title: 'Use a Number of Chunks',
description: 'Number of chunks of equal\n' + 'length in the output.',
title: 'split.chunksTitle',
description: 'split.chunksDescription',
type: 'chunks'
}
];
const outputOptions: {
description: string;
description: ParseKeys<'string'>;
accessor: keyof typeof initialValues;
}[] = [
{
description:
'Character that will be put\n' +
'between the split chunks.\n' +
'(It\'s newline "\\n" by default.)',
description: 'split.outputSeparatorDescription',
accessor: 'outputSeparator'
},
{
description: 'Character before each chunk',
description: 'split.charBeforeChunkDescription',
accessor: 'charBeforeChunk'
},
{
description: 'Character after each chunk',
description: 'split.charAfterChunkDescription',
accessor: 'charAfterChunk'
}
];
@@ -183,9 +174,9 @@ export default function SplitText({ title }: ToolComponentProps) {
<RadioWithTextField
key={type}
checked={type === values.splitSeparatorType}
title={t(`split.${type}Title`)}
title={t(title)}
fieldName={'splitSeparatorType'}
description={t(`split.${type}Description`)}
description={t(description)}
value={values[`${type}Value`]}
onRadioClick={() => updateField('splitSeparatorType', type)}
onTextChange={(val) => updateField(`${type}Value`, val)}
@@ -199,8 +190,7 @@ export default function SplitText({ title }: ToolComponentProps) {
key={option.accessor}
value={values[option.accessor]}
onOwnChange={(value) => updateField(option.accessor, value)}
//@ts-ignore
description={t(`split.${option.accessor}Description`)}
description={t(option.description)}
/>
))
}