From f22bb8bd5787ef82882230fb76208c4a0775a5c4 Mon Sep 17 00:00:00 2001 From: AshAnand34 Date: Sat, 12 Jul 2025 23:02:35 -0700 Subject: [PATCH 01/29] feat: add internationalization support --- package-lock.json | 111 ++++++++-- package.json | 3 + src/components/Hero.tsx | 12 +- src/components/Navbar/index.tsx | 4 +- src/components/ToolHeader.tsx | 4 +- src/components/ToolLayout.tsx | 31 ++- src/components/examples/ToolExamples.tsx | 6 +- src/components/input/BaseFileInput.tsx | 12 +- src/components/input/InputFooter.tsx | 9 +- src/components/input/NumericInputWithUnit.tsx | 4 +- .../input/ToolMultipleAudioInput.tsx | 13 +- src/components/input/ToolMultiplePdfInput.tsx | 11 +- src/components/input/ToolTextInput.tsx | 10 +- src/components/options/ToolOptions.tsx | 4 +- src/components/result/ResultFooter.tsx | 8 +- src/components/result/ToolFileResult.tsx | 10 +- src/components/result/ToolMultiFileResult.tsx | 27 ++- src/components/result/ToolTextResult.tsx | 10 +- src/i18n/en.json | 187 +++++++++++++++++ src/i18n/index.ts | 45 ++++ src/index.tsx | 1 + src/pages/tools/audio/change-speed/index.tsx | 23 +- src/pages/tools/audio/change-speed/meta.ts | 28 ++- src/pages/tools/audio/extract-audio/index.tsx | 26 ++- src/pages/tools/audio/extract-audio/meta.ts | 7 +- src/pages/tools/audio/i18n/en.json | 28 +++ .../tools/csv/change-csv-separator/meta.ts | 19 +- src/pages/tools/csv/csv-to-json/index.tsx | 38 ++-- .../csv/find-incomplete-csv-records/index.tsx | 60 ++++-- .../csv/find-incomplete-csv-records/meta.ts | 9 +- src/pages/tools/csv/i18n/en.json | 63 ++++++ .../tools/csv/insert-csv-columns/index.tsx | 146 +++++++------ .../tools/image/generic/resize/index.tsx | 53 ++--- src/pages/tools/image/i18n/en.json | 40 ++++ src/pages/tools/json/escape-json/meta.ts | 16 +- src/pages/tools/json/i18n/en.json | 39 ++++ src/pages/tools/json/json-to-xml/meta.ts | 18 +- src/pages/tools/json/minify/index.tsx | 15 +- src/pages/tools/json/minify/meta.ts | 15 +- src/pages/tools/json/prettify/index.tsx | 25 ++- src/pages/tools/json/prettify/meta.ts | 15 +- src/pages/tools/json/stringify/meta.ts | 23 +- src/pages/tools/json/tsv-to-json/meta.ts | 19 +- src/pages/tools/json/validateJson/index.tsx | 30 +-- src/pages/tools/json/validateJson/meta.ts | 17 +- src/pages/tools/list/duplicate/index.tsx | 42 ++-- src/pages/tools/list/duplicate/meta.ts | 13 +- .../tools/list/find-most-popular/meta.ts | 13 +- src/pages/tools/list/find-unique/index.tsx | 51 +++-- src/pages/tools/list/find-unique/meta.ts | 12 +- src/pages/tools/list/group/index.tsx | 50 ++--- src/pages/tools/list/group/meta.ts | 10 +- src/pages/tools/list/i18n/en.json | 115 ++++++++++ src/pages/tools/list/reverse/index.tsx | 29 +-- src/pages/tools/list/reverse/meta.ts | 10 +- src/pages/tools/list/rotate/meta.ts | 11 +- src/pages/tools/list/shuffle/index.tsx | 24 ++- src/pages/tools/list/shuffle/meta.ts | 11 +- src/pages/tools/list/sort/index.tsx | 66 +++--- src/pages/tools/list/sort/meta.ts | 13 +- src/pages/tools/list/truncate/meta.ts | 13 +- src/pages/tools/list/unwrap/meta.ts | 13 +- src/pages/tools/list/wrap/index.tsx | 35 ++-- src/pages/tools/list/wrap/meta.ts | 13 +- .../number/arithmetic-sequence/index.tsx | 30 ++- .../tools/number/arithmetic-sequence/meta.ts | 25 +-- src/pages/tools/number/generate/index.tsx | 21 +- src/pages/tools/number/generate/meta.ts | 17 +- src/pages/tools/number/i18n/en.json | 45 ++++ src/pages/tools/number/sum/index.tsx | 35 ++-- src/pages/tools/number/sum/meta.ts | 17 +- src/pages/tools/pdf/compress-pdf/index.tsx | 59 +++--- src/pages/tools/pdf/compress-pdf/meta.ts | 7 +- src/pages/tools/pdf/i18n/en.json | 72 +++++++ src/pages/tools/pdf/merge-pdf/index.tsx | 13 +- src/pages/tools/pdf/merge-pdf/meta.ts | 7 +- src/pages/tools/pdf/pdf-to-epub/meta.ts | 7 +- src/pages/tools/pdf/protect-pdf/meta.ts | 7 +- src/pages/tools/pdf/rotate-pdf/index.tsx | 75 +++---- src/pages/tools/pdf/split-pdf/index.tsx | 33 ++- src/pages/tools/pdf/split-pdf/meta.ts | 7 +- src/pages/tools/string/base64/index.tsx | 23 +- src/pages/tools/string/base64/meta.ts | 7 +- src/pages/tools/string/censor/meta.ts | 8 +- .../tools/string/create-palindrome/meta.ts | 7 +- .../tools/string/extract-substring/meta.ts | 11 +- src/pages/tools/string/i18n/en.json | 196 ++++++++++++++++++ src/pages/tools/string/join/index.tsx | 25 ++- src/pages/tools/string/join/meta.ts | 17 +- src/pages/tools/string/palindrome/meta.ts | 7 +- src/pages/tools/string/quote/index.tsx | 29 ++- src/pages/tools/string/quote/meta.ts | 13 +- src/pages/tools/string/randomize-case/meta.ts | 13 +- .../string/remove-duplicate-lines/meta.ts | 7 +- src/pages/tools/string/repeat/index.tsx | 27 ++- src/pages/tools/string/repeat/meta.ts | 7 +- src/pages/tools/string/reverse/index.tsx | 29 ++- src/pages/tools/string/reverse/meta.ts | 7 +- src/pages/tools/string/rot13/index.tsx | 17 +- src/pages/tools/string/rotate/index.tsx | 27 ++- src/pages/tools/string/split/index.tsx | 16 +- src/pages/tools/string/split/meta.ts | 18 +- src/pages/tools/string/statistic/index.tsx | 48 +++-- src/pages/tools/string/statistic/meta.ts | 8 +- .../tools/string/text-replacer/index.tsx | 32 +-- src/pages/tools/string/to-morse/index.tsx | 23 +- src/pages/tools/string/to-morse/meta.ts | 7 +- src/pages/tools/string/truncate/index.tsx | 48 +++-- src/pages/tools/string/truncate/meta.ts | 17 +- src/pages/tools/string/uppercase/index.tsx | 15 +- src/pages/tools/string/uppercase/meta.ts | 13 +- .../tools/time/check-leap-years/index.tsx | 7 +- src/pages/tools/time/check-leap-years/meta.ts | 18 +- .../time/convert-days-to-hours/index.tsx | 13 +- .../tools/time/convert-days-to-hours/meta.ts | 19 +- .../tools/time/convert-hours-to-days/meta.ts | 19 +- .../time/convert-seconds-to-time/index.tsx | 13 +- .../time/convert-seconds-to-time/meta.ts | 19 +- .../time/convert-time-to-seconds/index.tsx | 7 +- .../time/convert-time-to-seconds/meta.ts | 19 +- src/pages/tools/time/crontab-guru/meta.ts | 28 +-- src/pages/tools/time/i18n/en.json | 74 +++++++ .../tools/time/time-between-dates/index.tsx | 25 +-- .../tools/time/time-between-dates/meta.ts | 24 +-- .../tools/time/truncate-clock-time/index.tsx | 35 ++-- .../tools/time/truncate-clock-time/meta.ts | 19 +- src/pages/tools/video/change-speed/index.tsx | 25 ++- src/pages/tools/video/change-speed/meta.ts | 17 +- src/pages/tools/video/compress/index.tsx | 18 +- src/pages/tools/video/compress/meta.ts | 7 +- src/pages/tools/video/crop-video/index.tsx | 60 +++--- src/pages/tools/video/crop-video/meta.ts | 20 +- src/pages/tools/video/flip/index.tsx | 12 +- src/pages/tools/video/flip/meta.ts | 17 +- .../tools/video/gif/change-speed/meta.ts | 17 +- src/pages/tools/video/i18n/en.json | 79 +++++++ src/pages/tools/video/loop/index.tsx | 15 +- src/pages/tools/video/loop/meta.ts | 15 +- src/pages/tools/video/rotate/index.tsx | 12 +- src/pages/tools/video/rotate/meta.ts | 15 +- src/pages/tools/video/trim/index.tsx | 12 +- src/pages/tools/video/trim/meta.ts | 15 +- src/pages/tools/video/video-to-gif/meta.ts | 18 +- src/pages/tools/xml/i18n/en.json | 36 ++++ src/pages/tools/xml/xml-beautifier/index.tsx | 18 +- src/pages/tools/xml/xml-beautifier/meta.ts | 15 +- src/pages/tools/xml/xml-validator/index.tsx | 9 +- src/pages/tools/xml/xml-validator/meta.ts | 15 +- src/tools/defineTool.tsx | 9 +- 149 files changed, 2807 insertions(+), 1045 deletions(-) create mode 100644 src/i18n/en.json create mode 100644 src/i18n/index.ts create mode 100644 src/pages/tools/audio/i18n/en.json create mode 100644 src/pages/tools/csv/i18n/en.json create mode 100644 src/pages/tools/image/i18n/en.json create mode 100644 src/pages/tools/json/i18n/en.json create mode 100644 src/pages/tools/list/i18n/en.json create mode 100644 src/pages/tools/number/i18n/en.json create mode 100644 src/pages/tools/pdf/i18n/en.json create mode 100644 src/pages/tools/string/i18n/en.json create mode 100644 src/pages/tools/time/i18n/en.json create mode 100644 src/pages/tools/video/i18n/en.json create mode 100644 src/pages/tools/xml/i18n/en.json diff --git a/package-lock.json b/package-lock.json index 5b698c2..7ce39b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 52860e1..db17368 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index c1c6487..9e8e267 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -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(''); const theme = useTheme(); const [filteredTools, setFilteredTools] = useState(tools); @@ -64,13 +66,13 @@ export default function Hero() { - Get Things Done Quickly with{' '} + {t('hero.title')}{' '} - OmniTools + {t('hero.brand')} @@ -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')} , diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 5b1bfcf..39fa477 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -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 = ({ 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 = ({ /> } > - Buy me a coffee + {t('navbar.buyMeACoffee')} ]; const drawerList = ( diff --git a/src/components/ToolHeader.tsx b/src/components/ToolHeader.tsx index 8d88f13..dfe42f6 100644 --- a/src/components/ToolHeader.tsx +++ b/src/components/ToolHeader.tsx @@ -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')} )} diff --git a/src/components/ToolLayout.tsx b/src/components/ToolLayout.tsx index 3da0837..f481c87 100644 --- a/src/components/ToolLayout.tsx +++ b/src/components/ToolLayout.tsx @@ -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' }} > - {`${title} - OmniTools`} + {`${toolTitle} - OmniTools`} {children} category.type === type)! - .rawTitle - )} tools`} + title={t('toolLayout.allToolsTitle', { + type: capitalizeFirstLetter( + getToolsByCategory().find((category) => category.type === type)! + .rawTitle + ) + })} toolCards={otherCategoryTools} /> diff --git a/src/components/examples/ToolExamples.tsx b/src/components/examples/ToolExamples.tsx index c1c00c6..1b87f16 100644 --- a/src/components/examples/ToolExamples.tsx +++ b/src/components/examples/ToolExamples.tsx @@ -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 = Omit< ExampleCardProps, @@ -24,6 +25,7 @@ export default function ToolExamples({ getGroups, setInput }: ExampleProps) { + const { t } = useTranslation(); const { setValues } = useFormikContext(); function changeInputResult(newInput: string | undefined, newOptions: T) { @@ -39,10 +41,10 @@ export default function ToolExamples({ - {`${title} Examples`} + {t('toolExamples.title', { title })} - {subtitle ?? 'Click to try!'} + {subtitle ?? t('toolExamples.subtitle')} diff --git a/src/components/input/BaseFileInput.tsx b/src/components/input/BaseFileInput.tsx index ebc3840..6502e6c 100644 --- a/src/components/input/BaseFileInput.tsx +++ b/src/components/input/BaseFileInput.tsx @@ -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(null); const [isDragging, setIsDragging] = useState(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 })} ) : ( - 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 })} )} diff --git a/src/components/input/InputFooter.tsx b/src/components/input/InputFooter.tsx index 5760022..3a4afa5 100644 --- a/src/components/input/InputFooter.tsx +++ b/src/components/input/InputFooter.tsx @@ -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 ( {handleCopy && ( )} {handleClear && ( )} diff --git a/src/components/input/NumericInputWithUnit.tsx b/src/components/input/NumericInputWithUnit.tsx index a545685..436d933 100644 --- a/src/components/input/NumericInputWithUnit.tsx +++ b/src/components/input/NumericInputWithUnit.tsx @@ -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: { + English + हिंदी + + + ); + const buttons: ReactNode[] = [ + languageSelector, Date: Sat, 12 Jul 2025 23:17:29 -0700 Subject: [PATCH 03/29] fix: bug with missing longDescription in i18n property fixed --- src/tools/defineTool.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/defineTool.tsx b/src/tools/defineTool.tsx index f34c27c..8721017 100644 --- a/src/tools/defineTool.tsx +++ b/src/tools/defineTool.tsx @@ -15,6 +15,7 @@ export interface ToolMeta { name: string; description: string; shortDescription: string; + longDescription?: string; }; } From 035eb2edd1fc0f0cc90d6cf9f10334f9697c458a Mon Sep 17 00:00:00 2001 From: AshAnand34 Date: Sat, 12 Jul 2025 23:28:37 -0700 Subject: [PATCH 04/29] feat: enhance Hero component with internationalization and update category translations --- src/components/Hero.tsx | 67 ++++++++++++++++++++++++++-------- src/i18n/en.json | 69 ++++++++++++++++++++++++++++++++++- src/i18n/hi.json | 69 ++++++++++++++++++++++++++++++++++- src/pages/home/Categories.tsx | 28 ++++++++++++-- 4 files changed, 212 insertions(+), 21 deletions(-) diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 9e8e267..029b0d5 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -34,26 +34,63 @@ const GroupHeader = styled('div')(({ theme }) => ({ const GroupItems = styled('ul')({ padding: 0 }); -const exampleTools: { label: string; url: string }[] = [ - { - label: 'Create a transparent image', - url: '/image-generic/create-transparent' - }, - { label: 'Prettify JSON', url: '/json/prettify' }, - { label: 'Change GIF speed', url: '/gif/change-speed' }, - { label: 'Sort a list', url: '/list/sort' }, - { label: 'Compress PNG', url: '/png/compress-png' }, - { label: 'Split a text', url: '/string/split' }, - { label: 'Split PDF', url: '/pdf/split-pdf' }, - { label: 'Trim video', url: '/video/trim' }, - { label: 'Calculate number sum', url: '/number/sum' } -]; + export default function Hero() { const { t } = useTranslation(); const [inputValue, setInputValue] = useState(''); const theme = useTheme(); const [filteredTools, setFilteredTools] = useState(tools); const navigate = useNavigate(); + + const exampleTools: { label: string; url: string; translationKey: string }[] = + [ + { + label: t('hero.examples.createTransparentImage'), + url: '/image-generic/create-transparent', + translationKey: 'hero.examples.createTransparentImage' + }, + { + label: t('hero.examples.prettifyJson'), + url: '/json/prettify', + translationKey: 'hero.examples.prettifyJson' + }, + { + label: t('hero.examples.changeGifSpeed'), + url: '/gif/change-speed', + translationKey: 'hero.examples.changeGifSpeed' + }, + { + label: t('hero.examples.sortList'), + url: '/list/sort', + translationKey: 'hero.examples.sortList' + }, + { + label: t('hero.examples.compressPng'), + url: '/png/compress-png', + translationKey: 'hero.examples.compressPng' + }, + { + label: t('hero.examples.splitText'), + url: '/string/split', + translationKey: 'hero.examples.splitText' + }, + { + label: t('hero.examples.splitPdf'), + url: '/pdf/split-pdf', + translationKey: 'hero.examples.splitPdf' + }, + { + label: t('hero.examples.trimVideo'), + url: '/video/trim', + translationKey: 'hero.examples.trimVideo' + }, + { + label: t('hero.examples.calculateNumberSum'), + url: '/number/sum', + translationKey: 'hero.examples.calculateNumberSum' + } + ]; + const handleInputChange = ( event: React.ChangeEvent<{}>, newInputValue: string @@ -146,7 +183,7 @@ export default function Hero() { xs={12} md={6} lg={4} - key={tool.label} + key={tool.translationKey} > = ArrayType extends readonly (infer ElementType)[] ? ElementType : never; @@ -18,10 +19,25 @@ const SingleCategory = function ({ category: ArrayElement>; index: number; }) { + const { t } = useTranslation(); const navigate = useNavigate(); const theme = useTheme(); const [hovered, setHovered] = useState(false); const toggleHover = () => setHovered((prevState) => !prevState); + + // Get translated category title and description + const categoryTitle = t(`categories.${category.type}.title`, category.title); + const categoryDescription = t( + `categories.${category.type}.description`, + category.description + ); + const seeAllText = t('categories.seeAll', 'See all {{title}}', { + title: categoryTitle + }); + const tryText = t('categories.try', 'Try {{title}}', { + title: category.example.title + }); + return ( - {category.title} + {categoryTitle} - {category.description} + {categoryDescription} @@ -71,7 +87,9 @@ const SingleCategory = function ({ fullWidth onClick={() => navigate('/categories/' + category.type)} variant={'contained'} - >{`See all ${category.title}`} + > + {seeAllText} + + > + {tryText} + From 21c4f44d4e90df6219fc1ac51edc0812a01073aa Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sun, 13 Jul 2025 11:25:05 +0100 Subject: [PATCH 05/29] fix: misc --- .idea/workspace.xml | 316 ++++++++++++------ package-lock.json | 67 +++- package.json | 2 + src/i18n/index.ts | 81 +++-- src/pages/tools/audio/change-speed/index.tsx | 14 +- src/pages/tools/audio/change-speed/meta.ts | 6 +- src/pages/tools/audio/extract-audio/index.tsx | 12 +- src/pages/tools/audio/extract-audio/meta.ts | 6 +- .../tools/csv/change-csv-separator/meta.ts | 6 +- src/pages/tools/csv/csv-to-json/index.tsx | 26 +- .../csv/find-incomplete-csv-records/index.tsx | 28 +- .../csv/find-incomplete-csv-records/meta.ts | 6 +- .../tools/csv/insert-csv-columns/index.tsx | 50 +-- .../tools/image/generic/resize/index.tsx | 40 +-- src/pages/tools/image/i18n/hi.json | 20 -- src/pages/tools/json/escape-json/meta.ts | 6 +- src/pages/tools/json/json-to-xml/meta.ts | 6 +- src/pages/tools/json/minify/index.tsx | 8 +- src/pages/tools/json/minify/meta.ts | 6 +- src/pages/tools/json/prettify/index.tsx | 18 +- src/pages/tools/json/prettify/meta.ts | 6 +- src/pages/tools/json/stringify/meta.ts | 6 +- src/pages/tools/json/tsv-to-json/meta.ts | 6 +- src/pages/tools/json/validateJson/index.tsx | 12 +- src/pages/tools/json/validateJson/meta.ts | 6 +- src/pages/tools/list/duplicate/index.tsx | 30 +- src/pages/tools/list/duplicate/meta.ts | 6 +- .../tools/list/find-most-popular/meta.ts | 6 +- src/pages/tools/list/find-unique/index.tsx | 30 +- src/pages/tools/list/find-unique/meta.ts | 6 +- src/pages/tools/list/group/index.tsx | 32 +- src/pages/tools/list/group/meta.ts | 6 +- src/pages/tools/list/reverse/index.tsx | 18 +- src/pages/tools/list/reverse/meta.ts | 6 +- src/pages/tools/list/rotate/meta.ts | 6 +- src/pages/tools/list/shuffle/index.tsx | 18 +- src/pages/tools/list/shuffle/meta.ts | 6 +- src/pages/tools/list/sort/index.tsx | 36 +- src/pages/tools/list/sort/meta.ts | 6 +- src/pages/tools/list/truncate/meta.ts | 6 +- src/pages/tools/list/unwrap/meta.ts | 6 +- src/pages/tools/list/wrap/index.tsx | 26 +- src/pages/tools/list/wrap/meta.ts | 6 +- .../number/arithmetic-sequence/index.tsx | 18 +- .../tools/number/arithmetic-sequence/meta.ts | 6 +- src/pages/tools/number/generate/index.tsx | 14 +- src/pages/tools/number/generate/meta.ts | 6 +- src/pages/tools/number/sum/index.tsx | 16 +- src/pages/tools/number/sum/meta.ts | 6 +- src/pages/tools/pdf/compress-pdf/index.tsx | 32 +- src/pages/tools/pdf/compress-pdf/meta.ts | 6 +- src/pages/tools/pdf/merge-pdf/index.tsx | 10 +- src/pages/tools/pdf/merge-pdf/meta.ts | 6 +- src/pages/tools/pdf/pdf-to-epub/meta.ts | 6 +- src/pages/tools/pdf/protect-pdf/meta.ts | 6 +- src/pages/tools/pdf/rotate-pdf/index.tsx | 32 +- src/pages/tools/pdf/split-pdf/index.tsx | 20 +- src/pages/tools/pdf/split-pdf/meta.ts | 6 +- src/pages/tools/string/base64/index.tsx | 14 +- src/pages/tools/string/base64/meta.ts | 6 +- src/pages/tools/string/censor/meta.ts | 8 +- .../tools/string/create-palindrome/meta.ts | 6 +- .../tools/string/extract-substring/meta.ts | 6 +- src/pages/tools/string/join/index.tsx | 16 +- src/pages/tools/string/join/meta.ts | 6 +- src/pages/tools/string/palindrome/meta.ts | 6 +- src/pages/tools/string/quote/index.tsx | 20 +- src/pages/tools/string/quote/meta.ts | 6 +- src/pages/tools/string/randomize-case/meta.ts | 6 +- .../string/remove-duplicate-lines/meta.ts | 6 +- src/pages/tools/string/repeat/index.tsx | 20 +- src/pages/tools/string/repeat/meta.ts | 6 +- src/pages/tools/string/reverse/index.tsx | 18 +- src/pages/tools/string/reverse/meta.ts | 6 +- src/pages/tools/string/rot13/index.tsx | 8 +- src/pages/tools/string/rotate/index.tsx | 18 +- src/pages/tools/string/split/index.tsx | 6 +- src/pages/tools/string/split/meta.ts | 6 +- src/pages/tools/string/statistic/index.tsx | 30 +- src/pages/tools/string/statistic/meta.ts | 8 +- .../tools/string/text-replacer/index.tsx | 24 +- src/pages/tools/string/to-morse/index.tsx | 12 +- src/pages/tools/string/to-morse/meta.ts | 6 +- src/pages/tools/string/truncate/index.tsx | 36 +- src/pages/tools/string/truncate/meta.ts | 6 +- src/pages/tools/string/uppercase/index.tsx | 4 +- src/pages/tools/string/uppercase/meta.ts | 6 +- .../tools/time/check-leap-years/index.tsx | 2 +- src/pages/tools/time/check-leap-years/meta.ts | 6 +- .../time/convert-days-to-hours/index.tsx | 12 +- .../tools/time/convert-days-to-hours/meta.ts | 6 +- .../tools/time/convert-hours-to-days/meta.ts | 6 +- .../time/convert-seconds-to-time/index.tsx | 8 +- .../time/convert-seconds-to-time/meta.ts | 6 +- .../time/convert-time-to-seconds/index.tsx | 2 +- .../time/convert-time-to-seconds/meta.ts | 6 +- src/pages/tools/time/crontab-guru/meta.ts | 6 +- .../tools/time/time-between-dates/index.tsx | 22 +- .../tools/time/time-between-dates/meta.ts | 6 +- .../tools/time/truncate-clock-time/index.tsx | 24 +- .../tools/time/truncate-clock-time/meta.ts | 6 +- src/pages/tools/video/change-speed/index.tsx | 12 +- src/pages/tools/video/change-speed/meta.ts | 6 +- src/pages/tools/video/compress/index.tsx | 16 +- src/pages/tools/video/compress/meta.ts | 6 +- src/pages/tools/video/crop-video/index.tsx | 34 +- src/pages/tools/video/crop-video/meta.ts | 6 +- src/pages/tools/video/flip/index.tsx | 8 +- src/pages/tools/video/flip/meta.ts | 6 +- .../tools/video/gif/change-speed/meta.ts | 2 +- src/pages/tools/video/loop/index.tsx | 10 +- src/pages/tools/video/loop/meta.ts | 6 +- src/pages/tools/video/rotate/index.tsx | 8 +- src/pages/tools/video/rotate/meta.ts | 6 +- src/pages/tools/video/trim/index.tsx | 10 +- src/pages/tools/video/trim/meta.ts | 6 +- src/pages/tools/video/video-to-gif/meta.ts | 6 +- src/pages/tools/xml/xml-beautifier/index.tsx | 8 +- src/pages/tools/xml/xml-beautifier/meta.ts | 6 +- src/pages/tools/xml/xml-validator/index.tsx | 6 +- src/pages/tools/xml/xml-validator/meta.ts | 6 +- 121 files changed, 987 insertions(+), 837 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 3f79c3d..1bfb20d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,12 +4,128 @@ @@ -153,7 +151,7 @@ export default function FindIncompleteCsvRecords({ onChange={(value) => updateField('emptyLines', value)} title={t('findIncompleteCsvRecords.deleteLinesWithNoData')} description={t( - 'csv:findIncompleteCsvRecords.deleteLinesWithNoDataDescription' + 'findIncompleteCsvRecords.deleteLinesWithNoDataDescription' )} /> @@ -162,7 +160,7 @@ export default function FindIncompleteCsvRecords({ onChange={(value) => updateField('emptyValues', value)} title={t('findIncompleteCsvRecords.findEmptyValues')} description={t( - 'csv:findIncompleteCsvRecords.findEmptyValuesDescription' + 'findIncompleteCsvRecords.findEmptyValuesDescription' )} /> @@ -179,7 +177,7 @@ export default function FindIncompleteCsvRecords({ type="number" inputProps={{ min: 1 }} description={t( - 'csv:findIncompleteCsvRecords.messageLimitDescription' + 'findIncompleteCsvRecords.messageLimitDescription' )} /> )} diff --git a/src/pages/tools/csv/i18n/en.json b/src/pages/tools/csv/i18n/en.json index 3e9fbe8..59f1a9a 100644 --- a/src/pages/tools/csv/i18n/en.json +++ b/src/pages/tools/csv/i18n/en.json @@ -1,34 +1,4 @@ { - "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.", @@ -59,5 +29,43 @@ "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." } + }, + "csvToJson": { + "inputTitle": "Input CSV", + "resultTitle": "Output JSON", + + "inputCsvFormat": "Input CSV Format", + "columnSeparator": "Column Separator (e.g., , ; \\t)", + "fieldQuote": "Field Quote (e.g., \")", + "commentSymbol": "Comment Symbol (e.g., #)", + + "conversionOptions": "Conversion Options", + "useHeaders": "Use Headers", + "useHeadersDescription": "Treat the first row as column headers", + "skipEmptyLines": "Skip Empty Lines", + "skipEmptyLinesDescription": "Ignore empty lines in the input CSV", + "dynamicTypes": "Dynamic Types", + "dynamicTypesDescription": "Automatically convert numbers and booleans", + + "errorParsing": "Error parsing CSV: {{error}}" + }, + "findIncompleteCsvRecords": { + "title": "Find Incomplete CSV Records", + "inputTitle": "Input CSV", + "resultTitle": "CSV Status", + "toolInfo": { + "title": "What is a {{title}}?" + }, + "csvInputOptions": "CSV Input Options", + "csvSeparatorDescription": "Enter the character used to delimit columns in the CSV input file.", + "quoteCharacterDescription": "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.", + "checkingOptions": "Checking Options", + "deleteLinesWithNoData": "Delete Lines with No Data", + "deleteLinesWithNoDataDescription": "Remove empty lines from CSV input file.", + "findEmptyValues": "Find Empty Values", + "findEmptyValuesDescription": "Display a message about CSV fields that are empty (These are not missing fields but fields that contain nothing).", + "limitNumberOfMessages": "Limit number of messages", + "messageLimitDescription": "Set the limit of number of messages in the output." } } diff --git a/src/pages/tools/csv/insert-csv-columns/index.tsx b/src/pages/tools/csv/insert-csv-columns/index.tsx index cb7b03d..055c13c 100644 --- a/src/pages/tools/csv/insert-csv-columns/index.tsx +++ b/src/pages/tools/csv/insert-csv-columns/index.tsx @@ -247,7 +247,7 @@ export default function InsertCsvColumns({ ]} onChange={(value) => updateField('customPostionOptions', value)} description={t( - 'csv:insertCsvColumns.customPositionOptionsDescription' + 'insertCsvColumns.customPositionOptionsDescription' )} /> )} diff --git a/src/pages/tools/list/group/index.tsx b/src/pages/tools/list/group/index.tsx index 7f8ae57..84377be 100644 --- a/src/pages/tools/list/group/index.tsx +++ b/src/pages/tools/list/group/index.tsx @@ -99,10 +99,8 @@ export default function FindUnique({ title }: ToolComponentProps) { updateField('splitOperatorType', type)} - title={t(`list.group.splitOperators.${type}.title`)} - description={t( - `list.group.splitOperators.${type}.description` - )} + title={t(`group.splitOperators.${type}.title`)} + description={t(`group.splitOperators.${type}.description`)} checked={values.splitOperatorType === type} /> ))} diff --git a/src/pages/tools/list/i18n/en.json b/src/pages/tools/list/i18n/en.json index 0903744..d71eebb 100644 --- a/src/pages/tools/list/i18n/en.json +++ b/src/pages/tools/list/i18n/en.json @@ -1,17 +1,4 @@ { - "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." - } - }, "findUnique": { "title": "Find Unique", "inputTitle": "Input List", @@ -29,19 +16,6 @@ "caseSensitiveItems": "Case Sensitive Items", "caseSensitiveItemsDescription": "Output items with different case as unique elements in the list." }, - "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.", @@ -159,5 +133,39 @@ "description": "This example shows how to duplicate a list with a fractional number of copies." } } + }, + "reverse": { + "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!" + }, + "splitterMode": "Splitter Mode", + "itemSeparator": "Item Separator", + "itemSeparatorDescription": "Set a delimiting symbol or regular expression.", + "outputListOptions": "Output List Options", + "outputSeparatorDescription": "Output list item separator.", + "inputTitle": "Input list", + "resultTitle": "Reversed list", + "splitOperators": { + "symbol": { + "title": "Use a Symbol for Splitting", + "description": "Delimit input list items with a character." + }, + "regex": { + "title": "Use a Regex for Splitting", + "description": "Delimit input list items with a regular expression." + } + } + }, + "shuffle": { + "title": "Shuffle", + "inputTitle": "Input list", + "resultTitle": "Shuffled list", + "inputListSeparator": "Input list separator", + "delimiterDescription": "Set a delimiting symbol or regular expression.", + "shuffledListLength": "Shuffled List Length", + "outputLengthDescription": "Output this many random items", + "shuffledListSeparator": "Shuffled List Separator", + "joinSeparatorDescription": "Use this separator in the randomized list." } } diff --git a/src/pages/tools/list/reverse/index.tsx b/src/pages/tools/list/reverse/index.tsx index f8ac241..0a6e5f9 100644 --- a/src/pages/tools/list/reverse/index.tsx +++ b/src/pages/tools/list/reverse/index.tsx @@ -128,8 +128,8 @@ export default function Reverse({ title }: ToolComponentProps) { updateField('splitOperatorType', type)} - title={t(`list.reverse.splitOperators.${type}.title`)} - description={t(`list.reverse.splitOperators.${type}.description`)} + title={t(`reverse.splitOperators.${type}.title`)} + description={t(`reverse.splitOperators.${type}.description`)} checked={values.splitOperatorType === type} /> ))} diff --git a/src/pages/tools/number/arithmetic-sequence/index.tsx b/src/pages/tools/number/arithmetic-sequence/index.tsx index e7143b3..498528d 100644 --- a/src/pages/tools/number/arithmetic-sequence/index.tsx +++ b/src/pages/tools/number/arithmetic-sequence/index.tsx @@ -97,25 +97,21 @@ export default function ArithmeticSequence({ title }: ToolComponentProps) { component: ( updateField('firstTerm', val)} type="number" /> updateField('commonDifference', val)} type="number" /> updateField('numberOfTerms', val)} type="number" diff --git a/src/pages/tools/number/i18n/en.json b/src/pages/tools/number/i18n/en.json index c6c4cfd..bf29bde 100644 --- a/src/pages/tools/number/i18n/en.json +++ b/src/pages/tools/number/i18n/en.json @@ -1,17 +1,4 @@ { - "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.", @@ -41,5 +28,27 @@ "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." } + }, + "sum": { + "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." + }, + "inputTitle": "Input", + "resultTitle": "Total", + "numberExtraction": "Number Extraction", + "runningSum": "Running Sum", + "printRunningSum": "Print Running Sum", + "printRunningSumDescription": "Display the sum as it's calculated step by step.", + "extractionTypes": { + "smart": { + "title": "Smart Sum", + "description": "Auto detect numbers in the input." + }, + "delimiter": { + "title": "Number Delimiter", + "description": "Customize the number separator here. (By default a line break.)" + } + } } } diff --git a/src/pages/tools/number/sum/index.tsx b/src/pages/tools/number/sum/index.tsx index 6d66182..d649f99 100644 --- a/src/pages/tools/number/sum/index.tsx +++ b/src/pages/tools/number/sum/index.tsx @@ -135,9 +135,9 @@ export default function SumNumbers({ title }: ToolComponentProps) { updateField('extractionType', type)} checked={values.extractionType === type} - description={t(`number.sum.extractionTypes.${type}.description`)} - title={t(`number.sum.extractionTypes.${type}.title`)} + description={t(`sum.extractionTypes.${type}.description`)} + title={t(`sum.extractionTypes.${type}.title`)} /> ) ) diff --git a/src/pages/tools/pdf/i18n/en.json b/src/pages/tools/pdf/i18n/en.json index 79218f5..bf264a5 100644 --- a/src/pages/tools/pdf/i18n/en.json +++ b/src/pages/tools/pdf/i18n/en.json @@ -78,5 +78,14 @@ "errorReadingPdf": "Failed to read PDF file. Please make sure it is a valid PDF.", "errorCompressingPdf": "Failed to compress PDF: {{error}}" + }, + "merge": { + "inputTitle": "Input PDF", + "resultTitle": "Output merged PDF", + "loadingText": "Extracting pages", + "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." + } } } diff --git a/src/pages/tools/string/i18n/en.json b/src/pages/tools/string/i18n/en.json index 94d196d..962756e 100644 --- a/src/pages/tools/string/i18n/en.json +++ b/src/pages/tools/string/i18n/en.json @@ -5,15 +5,6 @@ "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.", @@ -67,24 +58,6 @@ "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.", @@ -192,5 +165,32 @@ "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." } + }, + "reverse": { + "reversalOptions": "Reversal options", + "processMultiLine": "Process multi-line text", + "processMultiLineDescription": "Each line will be reversed independently", + "skipEmptyLines": "Skip empty lines", + "skipEmptyLinesDescription": "Empty lines will be removed from the output", + "trimWhitespace": "Trim whitespace", + "trimWhitespaceDescription": "Remove leading and trailing whitespace from each line", + "inputTitle": "Text to reverse", + "resultTitle": "Reversed text" + }, + "split": { + "resultTitle": "Text pieces", + "splitSeparatorOptions": "Split separator options", + "symbolTitle": "Use a Symbol for Splitting", + "symbolDescription": "Character that will be used to break text into parts.\n(Space by default.)", + "regexTitle": "Use a Regex for Splitting", + "regexDescription": "Regular expression that will be used to break text into parts.\n(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\nlength in the output.", + "outputSeparatorOptions": "Output separator options", + "outputSeparatorDescription": "Character that will be put between the split chunks.\n(It's newline \"\\n\" by default.)", + "charBeforeChunkDescription": "Character before each chunk", + "charAfterChunkDescription": "Character after each chunk" } } diff --git a/src/pages/tools/string/join/index.tsx b/src/pages/tools/string/join/index.tsx index 98089d8..da69a33 100644 --- a/src/pages/tools/string/join/index.tsx +++ b/src/pages/tools/string/join/index.tsx @@ -33,7 +33,7 @@ const mergeOptions = { const blankTrailingOptions: { title: string; description: string; - accessor: keyof InitialValuesType; + accessor: keyof Omit; }[] = [ { title: 'Delete Blank Lines', @@ -136,10 +136,10 @@ export default function JoinText({ title }: ToolComponentProps) { component: blankTrailingOptions.map((option) => ( updateField(option.accessor, value)} - description={t(`string.join.${option.accessor}Description`)} + description={t(`join.${option.accessor}Description`)} /> )) } diff --git a/src/pages/tools/string/split/index.tsx b/src/pages/tools/string/split/index.tsx index 89c49a1..a53e637 100644 --- a/src/pages/tools/string/split/index.tsx +++ b/src/pages/tools/string/split/index.tsx @@ -183,9 +183,9 @@ export default function SplitText({ title }: ToolComponentProps) { updateField('splitSeparatorType', type)} onTextChange={(val) => updateField(`${type}Value`, val)} @@ -199,7 +199,8 @@ export default function SplitText({ title }: ToolComponentProps) { key={option.accessor} value={values[option.accessor]} onOwnChange={(value) => updateField(option.accessor, value)} - description={t(`string.split.${option.accessor}Description`)} + //@ts-ignore + description={t(`split.${option.accessor}Description`)} /> )) } diff --git a/src/pages/tools/string/statistic/index.tsx b/src/pages/tools/string/statistic/index.tsx index 2377068..c803174 100644 --- a/src/pages/tools/string/statistic/index.tsx +++ b/src/pages/tools/string/statistic/index.tsx @@ -262,9 +262,7 @@ export default function Truncate({ checked={values.characterCount} onChange={(value) => updateField('characterCount', value)} title={t('statistic.characterFrequencyAnalysis')} - description={t( - 'string:statistic.characterFrequencyAnalysisDescription' - )} + description={t('statistic.characterFrequencyAnalysisDescription')} /> updateField('onlySecond', true)} checked={values.onlySecond} title={t('truncateClockTime.truncateOnlySeconds')} - description={t( - 'time:truncateClockTime.truncateOnlySecondsDescription' - )} + description={t('truncateClockTime.truncateOnlySecondsDescription')} /> updateField('onlySecond', false)} checked={!values.onlySecond} title={t('truncateClockTime.truncateMinutesAndSeconds')} description={t( - 'time:truncateClockTime.truncateMinutesAndSecondsDescription' + 'truncateClockTime.truncateMinutesAndSecondsDescription' )} /> diff --git a/src/pages/tools/video/flip/index.tsx b/src/pages/tools/video/flip/index.tsx index 31b3e7e..eef36b1 100644 --- a/src/pages/tools/video/flip/index.tsx +++ b/src/pages/tools/video/flip/index.tsx @@ -66,7 +66,7 @@ export default function FlipVideo({ title }: ToolComponentProps) { {orientationOptions.map((orientationOption) => ( { updateField('orientation', orientationOption.value); diff --git a/src/pages/tools/video/i18n/en.json b/src/pages/tools/video/i18n/en.json index 940a4bc..6e897ad 100644 --- a/src/pages/tools/video/i18n/en.json +++ b/src/pages/tools/video/i18n/en.json @@ -1,43 +1,4 @@ { - "compress": { - "title": "Compress Video", - "description": "Reduce video file size while maintaining quality.", - "inputTitle": "Input Video", - "resultTitle": "Compressed Video", - "compressionOptions": "Compression Options", - "qualityDescription": "Video quality (1-100)", - "qualityPlaceholder": "Quality", - "toolInfo": { - "title": "Video Compression", - "description": "This tool allows you to compress video files to reduce their size while maintaining acceptable quality. You can adjust the compression level to balance between file size and video quality." - } - }, - "rotate": { - "title": "Rotate Video", - "description": "Rotate video by specified degrees.", - "inputTitle": "Input Video", - "resultTitle": "Rotated Video", - "rotationOptions": "Rotation Options", - "rotationAngleDescription": "Rotation angle in degrees", - "anglePlaceholder": "Angle", - "toolInfo": { - "title": "Video Rotation", - "description": "This tool allows you to rotate video files by a specified angle. You can rotate videos by 90, 180, or 270 degrees, or any custom angle." - } - }, - "flip": { - "title": "Flip Video", - "description": "Flip video horizontally or vertically.", - "inputTitle": "Input Video", - "resultTitle": "Flipped Video", - "flipOptions": "Flip Options", - "horizontalFlip": "Horizontal Flip", - "verticalFlip": "Vertical Flip", - "toolInfo": { - "title": "Video Flip", - "description": "This tool allows you to flip video files horizontally or vertically. Horizontal flip creates a mirror effect, while vertical flip turns the video upside down." - } - }, "loop": { "title": "Loop Video", "description": "Create a looping video by repeating the original video multiple times.", @@ -82,5 +43,42 @@ "endTime": "End Time", "inputTitle": "Input Video", "resultTitle": "Trimmed Video" + }, + "changeSpeed": { + "newVideoSpeed": "New Video Speed", + "defaultMultiplier": "Default multiplier: 2 means 2x faster", + "inputTitle": "Input Video", + "settingSpeed": "Setting Speed", + "resultTitle": "Edited Video", + "toolInfo": { + "title": "What is a {{title}}?" + } + }, + "compress": { + "resolution": "Resolution", + "quality": "Quality (CRF)", + "lossless": "Lossless", + "default": "Default", + "worst": "Worst", + "inputTitle": "Input Video", + "resultTitle": "Compressed Video", + "loadingText": "Compressing video..." + }, + "flip": { + "orientation": "Orientation", + "horizontalLabel": "Horizontal (Mirror)", + "verticalLabel": "Vertical (Upside Down)", + "inputTitle": "Input Video", + "flippingVideo": "Flipping Video", + "resultTitle": "Flipped Video" + }, + "rotate": { + "rotation": "Rotation", + "90Degrees": "90° Clockwise", + "180Degrees": "180° (Upside down)", + "270Degrees": "270° (90° Counter-clockwise)", + "inputTitle": "Input Video", + "rotatingVideo": "Rotating Video", + "resultTitle": "Rotated Video" } } diff --git a/src/pages/tools/video/rotate/index.tsx b/src/pages/tools/video/rotate/index.tsx index 5f8b404..32c6664 100644 --- a/src/pages/tools/video/rotate/index.tsx +++ b/src/pages/tools/video/rotate/index.tsx @@ -63,7 +63,7 @@ export default function RotateVideo({ title }: ToolComponentProps) { {angleOptions.map((angleOption) => ( { updateField('rotation', angleOption.value); From 24e0e38b4af7126a98ea93da2159f50dc79be884 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sun, 13 Jul 2025 15:39:12 +0100 Subject: [PATCH 08/29] fix: tsc --- .idea/workspace.xml | 42 +++++++++--------------------- @types/i18n.d.ts | 28 ++------------------ src/components/ToolLayout.tsx | 10 ++++--- src/i18n/index.ts | 31 ++++++++++------------ src/pages/tools/audio/i18n/en.json | 3 +++ src/tools/defineTool.tsx | 9 ++++--- 6 files changed, 43 insertions(+), 80 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index b51b1b6..648248c 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,27 +6,11 @@ + + - - - - - - - - - - - - - - - - - - - + diff --git a/@types/i18n.d.ts b/@types/i18n.d.ts index 7238ada..efd6ca1 100644 --- a/@types/i18n.d.ts +++ b/@types/i18n.d.ts @@ -1,33 +1,9 @@ // types/i18next.d.ts import 'i18next'; -import enGlobal from '../src/i18n/en.json'; -import enList from '../src/pages/tools/list/i18n/en.json'; -import enString from '../src/pages/tools/string/i18n/en.json'; -import enCsv from '../src/pages/tools/csv/i18n/en.json'; -import enJson from '../src/pages/tools/json/i18n/en.json'; -import enPdf from '../src/pages/tools/pdf/i18n/en.json'; -import enImage from '../src/pages/tools/image/i18n/en.json'; -import enAudio from '../src/pages/tools/audio/i18n/en.json'; -import enVideo from '../src/pages/tools/video/i18n/en.json'; -import enNumber from '../src/pages/tools/number/i18n/en.json'; -import enTime from '../src/pages/tools/time/i18n/en.json'; -import enXml from '../src/pages/tools/xml/i18n/en.json'; +import { resources } from '../src/i18n'; declare module 'i18next' { interface CustomTypeOptions { - resources: { - translation: typeof enGlobal; - list: typeof enList; - string: typeof enString; - csv: typeof enCsv; - json: typeof enJson; - pdf: typeof enPdf; - image: typeof enImage; - audio: typeof enAudio; - video: typeof enVideo; - number: typeof enNumber; - time: typeof enTime; - xml: typeof enXml; - }; + resources: (typeof resources)['en']; } } diff --git a/src/components/ToolLayout.tsx b/src/components/ToolLayout.tsx index b628461..63eee44 100644 --- a/src/components/ToolLayout.tsx +++ b/src/components/ToolLayout.tsx @@ -8,6 +8,8 @@ import { getToolsByCategory } from '@tools/index'; import { capitalizeFirstLetter } from '../utils/string'; import { IconifyIcon } from '@iconify/react'; import { useTranslation } from 'react-i18next'; +import { ToolCategory } from '@tools/defineTool'; +import { FullI18nKey } from '../i18n'; export default function ToolLayout({ children, @@ -20,12 +22,12 @@ export default function ToolLayout({ title: string; description: string; icon?: IconifyIcon | string; - type: string; + type: ToolCategory; children: ReactNode; i18n?: { - name: string; - description: string; - shortDescription: string; + name: FullI18nKey; + description: FullI18nKey; + shortDescription: FullI18nKey; }; }) { const { t } = useTranslation(); diff --git a/src/i18n/index.ts b/src/i18n/index.ts index dd54037..82bc088 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,4 +1,4 @@ -import i18n from 'i18next'; +import i18n, { ParseKeys } from 'i18next'; import { initReactI18next } from 'react-i18next'; import enGlobal from './en.json'; import hiGlobal from './hi.json'; @@ -33,7 +33,7 @@ const locizeOptions = { version: 'latest' }; // Merge translations for demonstration; in a real app, use namespaces -const resources = { +export const resources = { en: { translation: enGlobal, list: enList, @@ -62,21 +62,18 @@ const resources = { time: hiTime, xml: hiXml } -}; +} as const; -i18n - // .use(Backend) - .use(initReactI18next) - .init({ - resources, - lng: 'en', - fallbackLng: 'en', - interpolation: { - escapeValue: false - }, - backend: locizeOptions, - saveMissing: true, // Send missing keys to Locize - updateMissing: true // Update keys in Locize - }); +export type I18nNamespaces = keyof (typeof resources)['en']; +export type FullI18nKey = `${string}:${ParseKeys}`; + +i18n.use(Backend).use(initReactI18next).init({ + resources, + lng: 'en', + fallbackLng: 'en', + backend: locizeOptions, + saveMissing: true, // Send missing keys to Locize + updateMissing: true // Update keys in Locize +}); export default i18n; diff --git a/src/pages/tools/audio/i18n/en.json b/src/pages/tools/audio/i18n/en.json index 6ff32d5..2e2ec8c 100644 --- a/src/pages/tools/audio/i18n/en.json +++ b/src/pages/tools/audio/i18n/en.json @@ -13,6 +13,9 @@ } }, "changeSpeed": { + "title": "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", "newAudioSpeed": "New Audio Speed", "speedDescription": "Default multiplier: 2 means 2x faster", "outputFormat": "Output Format", diff --git a/src/tools/defineTool.tsx b/src/tools/defineTool.tsx index 8721017..0955200 100644 --- a/src/tools/defineTool.tsx +++ b/src/tools/defineTool.tsx @@ -1,6 +1,7 @@ import ToolLayout from '../components/ToolLayout'; import React, { JSXElementConstructor, LazyExoticComponent } from 'react'; import { IconifyIcon } from '@iconify/react'; +import { FullI18nKey } from '../i18n'; export interface ToolMeta { path: string; @@ -12,10 +13,10 @@ export interface ToolMeta { shortDescription: string; longDescription?: string; i18n?: { - name: string; - description: string; - shortDescription: string; - longDescription?: string; + name: FullI18nKey; + description: FullI18nKey; + shortDescription: FullI18nKey; + longDescription?: FullI18nKey; }; } From ee32138b751a4f152d051c41bed3ad69804deedc Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sun, 13 Jul 2025 16:59:57 +0100 Subject: [PATCH 09/29] fix: tsc --- src/i18n/index.ts | 4 +- src/pages/tools/audio/i18n/en.json | 17 +++- src/pages/tools/csv/i18n/en.json | 52 ++++++++++-- src/pages/tools/json/i18n/en.json | 32 +++++++- src/pages/tools/list/i18n/en.json | 54 +++++++++++-- src/pages/tools/number/i18n/en.json | 13 ++- src/pages/tools/pdf/i18n/en.json | 43 +++++++--- src/pages/tools/string/censor/meta.ts | 3 +- src/pages/tools/string/i18n/en.json | 80 ++++++++++++++++--- src/pages/tools/string/statistic/meta.ts | 3 +- src/pages/tools/time/i18n/en.json | 39 ++++++--- .../tools/video/gif/change-speed/meta.ts | 6 +- src/pages/tools/video/i18n/en.json | 45 +++++++++-- src/pages/tools/xml/i18n/en.json | 8 +- 14 files changed, 329 insertions(+), 70 deletions(-) diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 82bc088..0cec8ab 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -65,7 +65,9 @@ export const resources = { } as const; export type I18nNamespaces = keyof (typeof resources)['en']; -export type FullI18nKey = `${string}:${ParseKeys}`; +export type FullI18nKey = { + [K in I18nNamespaces]: `${K}:${ParseKeys}`; +}[I18nNamespaces]; i18n.use(Backend).use(initReactI18next).init({ resources, diff --git a/src/pages/tools/audio/i18n/en.json b/src/pages/tools/audio/i18n/en.json index 2e2ec8c..f3caae7 100644 --- a/src/pages/tools/audio/i18n/en.json +++ b/src/pages/tools/audio/i18n/en.json @@ -10,7 +10,9 @@ "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." - } + }, + "title": "Extract audio", + "shortDescription": "Extract audio from video files (MP4, MOV, etc.) to AAC, MP3, or WAV." }, "changeSpeed": { "title": "Change audio speed", @@ -25,6 +27,17 @@ "toolInfo": { "title": "What is {{title}}?", "description": "" - } + }, + "title": "Change audio speed" + }, + "mergeAudio": { + "title": "Merge Audio", + "description": "Combine multiple audio files into a single audio file by concatenating them in sequence.", + "shortDescription": "Merge multiple audio files into one (MP3, AAC, WAV)." + }, + "trim": { + "title": "Trim Audio", + "description": "Cut and trim audio files to extract specific segments by specifying start and end times.", + "shortDescription": "Trim audio files to extract specific time segments (MP3, AAC, WAV)." } } diff --git a/src/pages/tools/csv/i18n/en.json b/src/pages/tools/csv/i18n/en.json index 59f1a9a..c522fb4 100644 --- a/src/pages/tools/csv/i18n/en.json +++ b/src/pages/tools/csv/i18n/en.json @@ -28,17 +28,17 @@ "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." - } + }, + "title": "Insert CSV columns", + "shortDescription": "Quickly insert one or more new columns anywhere in a CSV file." }, "csvToJson": { "inputTitle": "Input CSV", "resultTitle": "Output JSON", - "inputCsvFormat": "Input CSV Format", "columnSeparator": "Column Separator (e.g., , ; \\t)", "fieldQuote": "Field Quote (e.g., \")", "commentSymbol": "Comment Symbol (e.g., #)", - "conversionOptions": "Conversion Options", "useHeaders": "Use Headers", "useHeadersDescription": "Treat the first row as column headers", @@ -46,8 +46,10 @@ "skipEmptyLinesDescription": "Ignore empty lines in the input CSV", "dynamicTypes": "Dynamic Types", "dynamicTypesDescription": "Automatically convert numbers and booleans", - - "errorParsing": "Error parsing CSV: {{error}}" + "errorParsing": "Error parsing CSV: {{error}}", + "title": "Convert CSV to JSON", + "description": "Convert CSV files to JSON format with customizable options for delimiters, quotes, and output formatting. Support for headers, comments, and dynamic type conversion.", + "shortDescription": "Convert CSV data to JSON format." }, "findIncompleteCsvRecords": { "title": "Find Incomplete CSV Records", @@ -66,6 +68,44 @@ "findEmptyValues": "Find Empty Values", "findEmptyValuesDescription": "Display a message about CSV fields that are empty (These are not missing fields but fields that contain nothing).", "limitNumberOfMessages": "Limit number of messages", - "messageLimitDescription": "Set the limit of number of messages in the output." + "messageLimitDescription": "Set the limit of number of messages in the output.", + "title": "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." + }, + "changeCsvSeparator": { + "title": "Change CSV Separator", + "description": "Change the delimiter/separator in CSV files. Convert between different CSV formats like comma, semicolon, tab, or custom separators.", + "shortDescription": "Change CSV file delimiter" + }, + "csvRowsToColumns": { + "title": "Convert CSV Rows to Columns", + "description": "This tool converts rows of a CSV (Comma Separated Values) file into columns. It extracts the horizontal lines from the input CSV one by one, rotates them 90 degrees, and outputs them as vertical columns one after another, separated by commas.', longDescription: 'This tool converts rows of a CSV (Comma Separated Values) file into columns. For example, if the input CSV data has 6 rows, then the output will have 6 columns and the elements of the rows will be arranged from the top to bottom. In a well-formed CSV, the number of values in each row is the same. However, in cases when rows are missing fields, the program can fix them and you can choose from the available options: fill missing data with empty elements or replace missing data with custom elements, such as \"missing\", \"?\", or \"x\". During the conversion process, the tool also cleans the CSV file from unnecessary information, such as empty lines (these are lines without visible information) and comments. To help the tool correctly identify comments, in the options, you can specify the symbol at the beginning of a line that starts a comment. This symbol is typically a hash \"#\" or double slash \"//\". Csv-abulous!.", + "shortDescription": "Convert CSV rows to columns." + }, + "csvToTsv": { + "title": "Convert CSV to TSV", + "description": "Upload your CSV file in the form below and it will automatically get converted to a TSV file. In the tool options, you can customize the input CSV format – specify the field delimiter, quotation character, and comment symbol, as well as skip empty CSV lines, and choose whether to preserve CSV column headers.", + "shortDescription": "Convert CSV data to TSV format." + }, + "csvToXml": { + "title": "Convert CSV to XML", + "description": "Convert CSV files to XML format with customizable options.", + "shortDescription": "Convert CSV data to XML format." + }, + "csvToYaml": { + "title": "Convert CSV to YAML", + "description": "Just upload your CSV file in the form below and it will automatically get converted to a YAML file. In the tool options, you can specify the field delimiter character, field quote character, and comment character to adapt the tool to custom CSV formats. Additionally, you can select the output YAML format: one that preserves CSV headers or one that excludes CSV headers.", + "shortDescription": "Quickly convert a CSV file to a YAML file." + }, + "swapCsvColumns": { + "title": "Swap CSV Columns", + "description": "Just upload your CSV file in the form below, specify the columns to swap, and the tool will automatically change the positions of the specified columns in the output file. In the tool options, you can specify the column positions or names that you want to swap, as well as fix incomplete data and optionally remove empty records and records that have been commented out.", + "shortDescription": "Reorder CSV columns." + }, + "transposeCsv": { + "title": "Transpose CSV", + "description": "Just upload your CSV file in the form below, and this tool will automatically transpose your CSV. In the tool options, you can specify the character that starts the comment lines in the CSV to remove them. Additionally, if the CSV is incomplete (missing values), you can replace missing values with the empty character or a custom character.", + "shortDescription": "Quickly transpose a CSV file." } } diff --git a/src/pages/tools/json/i18n/en.json b/src/pages/tools/json/i18n/en.json index 0a68d44..7f99275 100644 --- a/src/pages/tools/json/i18n/en.json +++ b/src/pages/tools/json/i18n/en.json @@ -12,7 +12,9 @@ "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." - } + }, + "title": "Prettify JSON", + "shortDescription": "Format and beautify JSON code" }, "minify": { "title": "Minify JSON", @@ -22,7 +24,9 @@ "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": "Minify JSON", + "shortDescription": "Minify JSON by removing whitespace" }, "validateJson": { "title": "Validate JSON", @@ -34,6 +38,28 @@ "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": "Validate JSON", + "shortDescription": "Validate JSON code for errors" + }, + "escapeJson": { + "title": "Escape JSON", + "description": "Escape special characters in JSON strings. Convert JSON data to properly escaped format for safe transmission or storage.", + "shortDescription": "Escape special characters in JSON" + }, + "jsonToXml": { + "title": "JSON to XML", + "description": "Convert JSON data to XML format. Transform structured JSON objects into well-formed XML documents.", + "shortDescription": "Convert JSON to XML format" + }, + "stringify": { + "title": "Stringify JSON", + "description": "Convert JavaScript objects to JSON string format. Serialize data structures into JSON strings for storage or transmission.", + "shortDescription": "Convert objects to JSON string" + }, + "tsvToJson": { + "title": "TSV to JSON", + "description": "Convert TSV (Tab-Separated Values) data to JSON format. Transform tabular data into structured JSON objects.", + "shortDescription": "Convert TSV to JSON format" } } diff --git a/src/pages/tools/list/i18n/en.json b/src/pages/tools/list/i18n/en.json index d71eebb..6779ea1 100644 --- a/src/pages/tools/list/i18n/en.json +++ b/src/pages/tools/list/i18n/en.json @@ -14,7 +14,10 @@ "findAbsolutelyUniqueItems": "Find Absolutely Unique Items", "findAbsolutelyUniqueItemsDescription": "Display only those items of the list that exist in a single copy.", "caseSensitiveItems": "Case Sensitive Items", - "caseSensitiveItemsDescription": "Output items with different case as unique elements in the list." + "caseSensitiveItemsDescription": "Output items with different case as unique elements in the list.", + "title": "Find unique", + "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" }, "wrap": { "title": "Wrap List", @@ -33,7 +36,9 @@ "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": "Wrap", + "shortDescription": "Wrap list items with specified criteria" }, "sort": { "inputTitle": "Input list", @@ -67,7 +72,10 @@ "sortedItemProperties": "Sorted item properties", "joinSeparatorDescription": "Use this symbol as a joiner between items in a sorted list.", "removeDuplicates": "Remove duplicates", - "removeDuplicatesDescription": "Delete duplicate list items." + "removeDuplicatesDescription": "Delete duplicate list items.", + "title": "Sort", + "description": "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" }, "group": { "inputTitle": "Input list", @@ -95,7 +103,10 @@ "deleteEmptyItemsDescription": "Ignore empty items and don't include them in the groups.", "padNonFullGroups": "Pad Non-full Groups", "padNonFullGroupsDescription": "Fill non-full groups with a custom item (enter below).", - "paddingCharDescription": "Use this character or item to pad non-full groups." + "paddingCharDescription": "Use this character or item to pad non-full groups.", + "title": "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" }, "duplicate": { "toolInfo": { @@ -132,7 +143,10 @@ "title": "Fractional duplication", "description": "This example shows how to duplicate a list with a fractional number of copies." } - } + }, + "title": "Duplicate", + "description": "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" }, "reverse": { "toolInfo": { @@ -155,7 +169,10 @@ "title": "Use a Regex for Splitting", "description": "Delimit input list items with a regular expression." } - } + }, + "title": "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" }, "shuffle": { "title": "Shuffle", @@ -166,6 +183,29 @@ "shuffledListLength": "Shuffled List Length", "outputLengthDescription": "Output this many random items", "shuffledListSeparator": "Shuffled List Separator", - "joinSeparatorDescription": "Use this separator in the randomized list." + "joinSeparatorDescription": "Use this separator in the randomized list.", + "title": "Shuffle", + "description": "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" + }, + "findMostPopular": { + "title": "Find most popular", + "description": "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" + }, + "rotate": { + "title": "Rotate", + "description": "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" + }, + "truncate": { + "title": "Truncate", + "description": "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" + }, + "unwrap": { + "title": "Unwrap", + "description": "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" } } diff --git a/src/pages/tools/number/i18n/en.json b/src/pages/tools/number/i18n/en.json index bf29bde..06b257c 100644 --- a/src/pages/tools/number/i18n/en.json +++ b/src/pages/tools/number/i18n/en.json @@ -12,7 +12,9 @@ "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." - } + }, + "title": "Generate", + "shortDescription": "Generate random numbers in specified ranges" }, "arithmeticSequence": { "title": "Arithmetic Sequence", @@ -27,7 +29,9 @@ "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": "Arithmetic Sequence", + "shortDescription": "Generate arithmetic sequences" }, "sum": { "toolInfo": { @@ -49,6 +53,9 @@ "title": "Number Delimiter", "description": "Customize the number separator here. (By default a line break.)" } - } + }, + "title": "Sum", + "description": "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" } } diff --git a/src/pages/tools/pdf/i18n/en.json b/src/pages/tools/pdf/i18n/en.json index bf264a5..9adbebe 100644 --- a/src/pages/tools/pdf/i18n/en.json +++ b/src/pages/tools/pdf/i18n/en.json @@ -13,7 +13,9 @@ "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." - } + }, + "title": "Merge PDF", + "shortDescription": "Merge multiple PDF files into a single document" }, "splitPdf": { "title": "Split PDF", @@ -29,7 +31,9 @@ "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." - } + }, + "title": "Split PDF", + "shortDescription": "Extract specific pages from a PDF file" }, "rotatePdf": { "title": "Rotate PDF", @@ -53,31 +57,30 @@ "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)." - } + }, + "title": "Rotate PDF", + "shortDescription": "Rotate pages in a PDF document" }, "compressPdf": { "inputTitle": "Input PDF", "resultTitle": "Compressed PDF", "compressingPdf": "Compressing PDF...", - "compressionSettings": "Compression Settings", "compressionLevel": "Compression Level", - "lowCompression": "Low Compression", "lowCompressionDescription": "Slightly reduce file size with minimal quality loss", - "mediumCompression": "Medium Compression", "mediumCompressionDescription": "Balance between file size and quality", - "highCompression": "High Compression", "highCompressionDescription": "Maximum file size reduction with some quality loss", - "fileSize": "Original File Size", "compressedFileSize": "Compressed File Size", "pages": "Number of Pages", - "errorReadingPdf": "Failed to read PDF file. Please make sure it is a valid PDF.", - "errorCompressingPdf": "Failed to compress PDF: {{error}}" + "errorCompressingPdf": "Failed to compress PDF: {{error}}", + "title": "Compress PDF", + "description": "Reduce PDF file size while maintaining quality using Ghostscript", + "shortDescription": "Compress PDF files securely in your browser" }, "merge": { "inputTitle": "Input PDF", @@ -87,5 +90,25 @@ "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." } + }, + "editor": { + "title": "PDF Editor", + "description": "Advanced PDF editor with annotation, form-fill, highlight, and export capabilities. Edit your PDFs directly in the browser with professional-grade tools including text insertion, drawing, highlighting, signing and form filling.", + "shortDescription": "Edit PDFs with advanced annotation, signing and editing tools" + }, + "pdfToEpub": { + "title": "PDF to EPUB", + "description": "Transform PDF documents into EPUB files for better e-reader compatibility.', icon: 'material-symbols:import-contacts', component: lazy(() => import('./index')), keywords: ['pdf', 'epub', 'convert', 'ebook'], path: 'pdf-to-epub', i18n: { name: 'pdf:pdfToEpub.title', description: 'pdf:pdfToEpub.description", + "shortDescription": "Convert PDF files to EPUB format" + }, + "pdfToPng": { + "title": "PDF to PNG", + "description": "Transform PDF documents into PNG panels.", + "shortDescription": "Convert PDF into PNG images" + }, + "protectPdf": { + "title": "Protect PDF", + "description": "Add password protection to your PDF files securely in your browser", + "shortDescription": "Password protect PDF files securely" } } diff --git a/src/pages/tools/string/censor/meta.ts b/src/pages/tools/string/censor/meta.ts index de271b0..e9a72b8 100644 --- a/src/pages/tools/string/censor/meta.ts +++ b/src/pages/tools/string/censor/meta.ts @@ -16,7 +16,6 @@ export const tool = defineTool('string', { i18n: { name: 'string:censor.title', description: 'string:censor.description', - shortDescription: 'string:censor.shortDescription', - longDescription: 'string:censor.longDescription' + shortDescription: 'string:censor.shortDescription' } }); diff --git a/src/pages/tools/string/i18n/en.json b/src/pages/tools/string/i18n/en.json index 962756e..627d845 100644 --- a/src/pages/tools/string/i18n/en.json +++ b/src/pages/tools/string/i18n/en.json @@ -3,7 +3,9 @@ "title": "Convert to Uppercase", "description": "Convert text to uppercase letters.", "inputTitle": "Input text", - "resultTitle": "Uppercase text" + "resultTitle": "Uppercase text", + "title": "Uppercase", + "shortDescription": "Convert text to uppercase" }, "base64": { "title": "Base64 Encoder/Decoder", @@ -16,7 +18,9 @@ "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": "Base64", + "shortDescription": "Encode or decode data using Base64." }, "truncate": { "title": "Truncate Text", @@ -40,7 +44,9 @@ "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." - } + }, + "title": "Truncate", + "shortDescription": "Truncate text to a specified length" }, "quote": { "title": "Text Quoter", @@ -56,7 +62,9 @@ "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": "Quote", + "shortDescription": "Add quotes around text with various styles" }, "join": { "title": "Join Text", @@ -74,7 +82,9 @@ "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": "Join", + "shortDescription": "Join text elements with a specified separator" }, "rotate": { "title": "Rotate Text", @@ -89,7 +99,9 @@ "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": "Rotate", + "shortDescription": "Shift characters in text by position." }, "repeat": { "title": "Repeat Text", @@ -105,7 +117,9 @@ "toolInfo": { "title": "Repeat text", "description": "This tool allows you to repeat a given text multiple times with an optional separator." - } + }, + "title": "Repeat text", + "shortDescription": "Repeat text multiple times" }, "rot13": { "title": "ROT13 Encoder/Decoder", @@ -115,7 +129,9 @@ "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": "Rot13", + "shortDescription": "Encode or decode text using ROT13 cipher." }, "toMorse": { "title": "To Morse", @@ -124,7 +140,9 @@ "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." + "dashSymbolDescription": "Symbol that will correspond to the dash in Morse code.", + "title": "String To morse", + "shortDescription": "Quickly encode text to morse" }, "statistic": { "title": "Text Statistics", @@ -146,7 +164,9 @@ "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." - } + }, + "title": "Text Statistics", + "shortDescription": "Get statistics about your text" }, "textReplacer": { "title": "Text Replacer", @@ -175,7 +195,10 @@ "trimWhitespace": "Trim whitespace", "trimWhitespaceDescription": "Remove leading and trailing whitespace from each line", "inputTitle": "Text to reverse", - "resultTitle": "Reversed text" + "resultTitle": "Reversed text", + "title": "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" }, "split": { "resultTitle": "Text pieces", @@ -191,6 +214,39 @@ "outputSeparatorOptions": "Output separator options", "outputSeparatorDescription": "Character that will be put between the split chunks.\n(It's newline \"\\n\" by default.)", "charBeforeChunkDescription": "Character before each chunk", - "charAfterChunkDescription": "Character after each chunk" + "charAfterChunkDescription": "Character after each chunk", + "title": "Split", + "description": "World's simplest browser-based utility for splitting text. Input your text and specify a separator to split it into multiple parts. Perfect for data processing, text manipulation, or extracting specific content from larger text blocks.", + "shortDescription": "Split text into multiple parts using a separator" + }, + "censor": { + "title": "Text Censor", + "description": "utility for censoring words in text. Load your text in the input form on the left, specify all the bad words in the options, and you'll instantly get censored text in the output area.\", 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')), i18n: { name: 'string:censor.title', description: 'string:censor.description", + "shortDescription": "Quickly mask bad words or replace them with alternative words." + }, + "createPalindrome": { + "title": "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" + }, + "extractSubstring": { + "title": "Extract substring", + "description": "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" + }, + "palindrome": { + "title": "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" + }, + "randomizeCase": { + "title": "Randomize case", + "description": "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" + }, + "removeDuplicateLines": { + "title": "Remove duplicate lines", + "description": "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" } } diff --git a/src/pages/tools/string/statistic/meta.ts b/src/pages/tools/string/statistic/meta.ts index 4cb9309..3a6aa91 100644 --- a/src/pages/tools/string/statistic/meta.ts +++ b/src/pages/tools/string/statistic/meta.ts @@ -15,7 +15,6 @@ export const tool = defineTool('string', { i18n: { name: 'string:statistic.title', description: 'string:statistic.description', - shortDescription: 'string:statistic.shortDescription', - longDescription: 'string:statistic.longDescription' + shortDescription: 'string:statistic.shortDescription' } }); diff --git a/src/pages/tools/time/i18n/en.json b/src/pages/tools/time/i18n/en.json index 71ea9f7..6757169 100644 --- a/src/pages/tools/time/i18n/en.json +++ b/src/pages/tools/time/i18n/en.json @@ -7,7 +7,9 @@ "toolInfo": { "title": "What is a Leap Year?", "description": "A leap year is a year containing one additional day (February 29) to keep the calendar year synchronized with the astronomical year. Leap years occur every 4 years, except for years that are divisible by 100 but not by 400." - } + }, + "title": "Check Leap Years", + "shortDescription": "Check if a year is a leap year" }, "convertDaysToHours": { "title": "Convert Days to Hours", @@ -18,7 +20,9 @@ "toolInfo": { "title": "Convert Days to Hours", "description": "This tool allows you to convert days to hours. You can input days as numbers or with units, and the tool will convert them to hours. You can also choose to append the 'hours' suffix to the output values." - } + }, + "title": "Convert Days to Hours", + "shortDescription": "Convert days to hours" }, "convertHoursToDays": { "title": "Convert Hours to Days", @@ -29,7 +33,9 @@ "toolInfo": { "title": "Convert Hours to Days", "description": "This tool allows you to convert hours to days. You can input hours as numbers or with units, and the tool will convert them to days. You can also choose to append the 'days' suffix to the output values." - } + }, + "title": "Convert Hours to Days", + "shortDescription": "Convert hours to days" }, "convertTimeToSeconds": { "title": "Convert Time to Seconds", @@ -39,26 +45,28 @@ "toolInfo": { "title": "Convert Time to Seconds", "description": "This tool allows you to convert formatted time strings (HH:MM:SS) to seconds. It's useful for calculating durations and time intervals." - } + }, + "title": "Convert Time to Seconds", + "shortDescription": "Convert time format to seconds" }, "truncateClockTime": { "toolInfo": { "title": "What is a {{title}}?" }, - "truncationSide": "Truncation Side", "truncateOnlySeconds": "Truncate Only Seconds", "truncateOnlySecondsDescription": "Drop the seconds component from each clock time.", "truncateMinutesAndSeconds": "Truncate Minutes and Seconds", "truncateMinutesAndSecondsDescription": "Drop both – the minutes and seconds components from each clock time.", - "printDroppedComponents": "Print Dropped Components", "zeroPrintTruncatedParts": "Zero-print Truncated Parts", "zeroPrintDescription": "Display the dropped parts as zero values \"00\".", - "timePadding": "Time Padding", "useZeroPadding": "Use Zero Padding", - "zeroPaddingDescription": "Make all time components always be two digits wide." + "zeroPaddingDescription": "Make all time components always be two digits wide.", + "title": "Truncate Clock Time", + "description": "Truncate clock time to remove seconds or minutes. Round time to the nearest hour, minute, or custom interval.", + "shortDescription": "Truncate clock time to specified precision" }, "timeBetweenDates": { "title": "Time Between Dates", @@ -73,7 +81,10 @@ "endDateTime": "End Date & Time", "endDate": "End Date", "endTime": "End Time", - "endTimezone": "End Timezone" + "endTimezone": "End Timezone", + "title": "Time Between Dates", + "description": "Calculate the time difference between two dates. Get the exact duration in days, hours, minutes, and seconds.", + "shortDescription": "Calculate time between two dates" }, "convertSecondsToTime": { "timePadding": "Time Padding", @@ -82,6 +93,14 @@ "toolInfo": { "title": "What is a {{title}}?", "description": "" - } + }, + "title": "Convert Seconds to Time", + "description": "Convert seconds to a readable time format (hours:minutes:seconds). Enter the number of seconds to get the formatted time.", + "shortDescription": "Convert seconds to time format" + }, + "crontabGuru": { + "title": "Crontab Guru", + "description": "Generate and understand cron expressions. Create cron schedules for automated tasks and system jobs.", + "shortDescription": "Generate and understand cron expressions" } } diff --git a/src/pages/tools/video/gif/change-speed/meta.ts b/src/pages/tools/video/gif/change-speed/meta.ts index 3e36c2d..5433e0a 100644 --- a/src/pages/tools/video/gif/change-speed/meta.ts +++ b/src/pages/tools/video/gif/change-speed/meta.ts @@ -12,8 +12,8 @@ export const tool = defineTool('gif', { keywords: ['gif', 'speed', 'animation', 'fast', 'slow'], component: lazy(() => import('./index')), i18n: { - name: 'gif.changeSpeed.title', - description: 'gif.changeSpeed.description', - shortDescription: 'gif.changeSpeed.shortDescription' + name: 'video:gif.changeSpeed.title', + description: 'video:gif.changeSpeed.description', + shortDescription: 'video:gif.changeSpeed.shortDescription' } }); diff --git a/src/pages/tools/video/i18n/en.json b/src/pages/tools/video/i18n/en.json index 6e897ad..98a5069 100644 --- a/src/pages/tools/video/i18n/en.json +++ b/src/pages/tools/video/i18n/en.json @@ -10,7 +10,9 @@ "toolInfo": { "title": "What is a {{title}}?", "description": "This tool allows you to create a looping video by repeating the original video multiple times. You can specify how many times the video should loop." - } + }, + "title": "Loop Video", + "shortDescription": "Create looping video files" }, "cropVideo": { "title": "Crop Video", @@ -35,14 +37,19 @@ "toolInfo": { "title": "Crop Video", "description": "This tool allows you to crop video files to remove unwanted areas. You can specify the crop area by setting the X, Y coordinates and width, height dimensions." - } + }, + "title": "Crop Video", + "shortDescription": "Crop video to remove unwanted areas" }, "trim": { "timestamps": "Timestamps", "startTime": "Start Time", "endTime": "End Time", "inputTitle": "Input Video", - "resultTitle": "Trimmed Video" + "resultTitle": "Trimmed Video", + "title": "Trim Video", + "description": "Trim video files by specifying start and end times. Remove unwanted sections from the beginning or end of videos.", + "shortDescription": "Trim video by removing unwanted sections" }, "changeSpeed": { "newVideoSpeed": "New Video Speed", @@ -52,7 +59,10 @@ "resultTitle": "Edited Video", "toolInfo": { "title": "What is a {{title}}?" - } + }, + "title": "Change Video Speed", + "description": "Change the playback speed of video files. Speed up or slow down videos while maintaining audio synchronization. Supports various speed multipliers and common video formats.", + "shortDescription": "Change video playback speed" }, "compress": { "resolution": "Resolution", @@ -62,7 +72,10 @@ "worst": "Worst", "inputTitle": "Input Video", "resultTitle": "Compressed Video", - "loadingText": "Compressing video..." + "loadingText": "Compressing video...", + "title": "Compress Video", + "description": "Compress videos by scaling them to different resolutions like 240p, 480p, 720p, etc. This tool helps reduce file size while maintaining acceptable quality. Supports common video formats like MP4, WebM, and OGG.", + "shortDescription": "Compress videos by scaling to different resolutions" }, "flip": { "orientation": "Orientation", @@ -70,7 +83,10 @@ "verticalLabel": "Vertical (Upside Down)", "inputTitle": "Input Video", "flippingVideo": "Flipping Video", - "resultTitle": "Flipped Video" + "resultTitle": "Flipped Video", + "title": "Flip Video", + "description": "Flip video files horizontally or vertically. Mirror videos for special effects or correct orientation issues.", + "shortDescription": "Flip video horizontally or vertically" }, "rotate": { "rotation": "Rotation", @@ -79,6 +95,21 @@ "270Degrees": "270° (90° Counter-clockwise)", "inputTitle": "Input Video", "rotatingVideo": "Rotating Video", - "resultTitle": "Rotated Video" + "resultTitle": "Rotated Video", + "title": "Rotate Video", + "description": "Rotate video files by 90, 180, or 270 degrees. Correct video orientation or create special effects with precise rotation control.", + "shortDescription": "Rotate video by specified degrees" + }, + "videoToGif": { + "title": "Video to GIF", + "description": "Convert video files to animated GIF format. Extract specific time ranges and create shareable animated images.", + "shortDescription": "Convert video to animated GIF" + }, + "gif": { + "changeSpeed": { + "title": "Change GIF Speed", + "description": "Change the playback speed of GIF animations. Speed up or slow down GIFs while maintaining smooth animation.", + "shortDescription": "Change GIF animation speed" + } } } diff --git a/src/pages/tools/xml/i18n/en.json b/src/pages/tools/xml/i18n/en.json index 3d900f3..237fb0b 100644 --- a/src/pages/tools/xml/i18n/en.json +++ b/src/pages/tools/xml/i18n/en.json @@ -12,7 +12,9 @@ "toolInfo": { "title": "XML Beautifier", "description": "This tool allows you to format XML data with proper indentation and spacing, making it more readable and easier to work with." - } + }, + "title": "XML Beautifier", + "shortDescription": "Format and beautify XML code" }, "xmlValidator": { "title": "XML Validator", @@ -21,7 +23,9 @@ "toolInfo": { "title": "XML Validator", "description": "This tool allows you to validate XML syntax and structure. It checks if the XML is well-formed and provides detailed error messages for any issues found." - } + }, + "title": "XML Validator", + "shortDescription": "Validate XML code for errors" }, "xmlViewer": { "title": "XML Viewer", From be53b60bd36899fe71986adcfe191ebfb2487206 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Sun, 13 Jul 2025 17:17:35 +0100 Subject: [PATCH 10/29] chore: locize upload --- scripts/locize-upload.js | 213 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 scripts/locize-upload.js diff --git a/scripts/locize-upload.js b/scripts/locize-upload.js new file mode 100644 index 0000000..6099a3d --- /dev/null +++ b/scripts/locize-upload.js @@ -0,0 +1,213 @@ +// one-time-upload.js +// Simple script to upload your existing translations to Locize once + +const fs = require('fs'); +const https = require('https'); + +// Configuration +const LOCIZE_PROJECT_ID = 'e7156a3e-66fb-4035-a0f0-cebf1c63a3ba'; +const LOCIZE_API_KEY = process.env.LOCIZE_API_KEY; // Replace with your actual API key +const LOCIZE_VERSION = 'latest'; + +// Define your translation files +const translationFiles = [ + // English translations + { lang: 'en', namespace: 'translation', file: '../src/i18n/en.json' }, + { + lang: 'en', + namespace: 'list', + file: '../src/pages/tools/list/i18n/en.json' + }, + { + lang: 'en', + namespace: 'string', + file: '../src/pages/tools/string/i18n/en.json' + }, + { lang: 'en', namespace: 'csv', file: '../src/pages/tools/csv/i18n/en.json' }, + { + lang: 'en', + namespace: 'json', + file: '../src/pages/tools/json/i18n/en.json' + }, + { lang: 'en', namespace: 'pdf', file: '../src/pages/tools/pdf/i18n/en.json' }, + { + lang: 'en', + namespace: 'image', + file: '../src/pages/tools/image/i18n/en.json' + }, + { + lang: 'en', + namespace: 'audio', + file: '../src/pages/tools/audio/i18n/en.json' + }, + { + lang: 'en', + namespace: 'video', + file: '../src/pages/tools/video/i18n/en.json' + }, + { + lang: 'en', + namespace: 'number', + file: '../src/pages/tools/number/i18n/en.json' + }, + { + lang: 'en', + namespace: 'time', + file: '../src/pages/tools/time/i18n/en.json' + }, + { lang: 'en', namespace: 'xml', file: '../src/pages/tools/xml/i18n/en.json' }, + + // Hindi translations + { lang: 'hi', namespace: 'translation', file: '../src/i18n/hi.json' }, + { + lang: 'hi', + namespace: 'list', + file: '../src/pages/tools/list/i18n/hi.json' + }, + { + lang: 'hi', + namespace: 'string', + file: '../src/pages/tools/string/i18n/hi.json' + }, + { lang: 'hi', namespace: 'csv', file: '../src/pages/tools/csv/i18n/hi.json' }, + { + lang: 'hi', + namespace: 'json', + file: '../src/pages/tools/json/i18n/hi.json' + }, + { lang: 'hi', namespace: 'pdf', file: '../src/pages/tools/pdf/i18n/hi.json' }, + { + lang: 'hi', + namespace: 'image', + file: '../src/pages/tools/image/i18n/hi.json' + }, + { + lang: 'hi', + namespace: 'audio', + file: '../src/pages/tools/audio/i18n/hi.json' + }, + { + lang: 'hi', + namespace: 'video', + file: '../src/pages/tools/video/i18n/hi.json' + }, + { + lang: 'hi', + namespace: 'number', + file: '../src/pages/tools/number/i18n/hi.json' + }, + { + lang: 'hi', + namespace: 'time', + file: '../src/pages/tools/time/i18n/hi.json' + }, + { lang: 'hi', namespace: 'xml', file: '../src/pages/tools/xml/i18n/hi.json' } +]; + +function flattenJson(obj, prefix = '') { + const flattened = {}; + + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + const newKey = prefix ? `${prefix}.${key}` : key; + + if ( + typeof obj[key] === 'object' && + obj[key] !== null && + !Array.isArray(obj[key]) + ) { + // Recursively flatten nested objects + Object.assign(flattened, flattenJson(obj[key], newKey)); + } else { + // It's a primitive value or array + flattened[newKey] = obj[key]; + } + } + } + + return flattened; +} + +function uploadToLocize(lang, namespace, data) { + return new Promise((resolve, reject) => { + // Flatten the JSON structure for Locize API + const flattenedData = flattenJson(data); + const postData = JSON.stringify(flattenedData); + + const options = { + hostname: 'api.locize.app', + port: 443, + path: `/update/${LOCIZE_PROJECT_ID}/${LOCIZE_VERSION}/${lang}/${namespace}`, + method: 'POST', + headers: { + Authorization: `Bearer ${LOCIZE_API_KEY}`, + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData) + } + }; + + const req = https.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => (data += chunk)); + res.on('end', () => { + if (res.statusCode === 200) { + resolve(JSON.parse(data)); + } else { + reject(new Error(`HTTP ${res.statusCode}: ${data}`)); + } + }); + }); + + req.on('error', reject); + req.write(postData); + req.end(); + }); +} + +async function main() { + console.log('Starting one-time upload to Locize...\n'); + + let successCount = 0; + let errorCount = 0; + + for (const { lang, namespace, file } of translationFiles) { + try { + // Check if file exists + if (!fs.existsSync(file)) { + console.log(`⚠️ File not found: ${file}`); + continue; + } + + // Read translation file + const translations = JSON.parse(fs.readFileSync(file, 'utf8')); + const flattenedTranslations = flattenJson(translations); + const keyCount = Object.keys(flattenedTranslations).length; + + if (keyCount === 0) { + console.log(`⚠️ Empty file: ${lang}/${namespace}`); + continue; + } + + // Upload to Locize + await uploadToLocize(lang, namespace, translations); + console.log(`✅ ${lang}/${namespace} - ${keyCount} keys uploaded`); + successCount++; + } catch (error) { + console.error(`❌ ${lang}/${namespace} - Error: ${error.message}`); + errorCount++; + } + } + + console.log('\n=== Upload Summary ==='); + console.log(`✅ Successful uploads: ${successCount}`); + console.log(`❌ Failed uploads: ${errorCount}`); + console.log(`📊 Total files processed: ${successCount + errorCount}`); + + if (errorCount === 0) { + console.log('\n🎉 All translations uploaded successfully!'); + console.log('You can now view them in your Locize dashboard.'); + } +} + +// Run the upload +main().catch(console.error); From c138b65bbd2dcc64ff643caab722ce6106625927 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Mon, 14 Jul 2025 12:47:05 +0100 Subject: [PATCH 11/29] chore: i18n in meta --- src/i18n/index.ts | 5 +---- src/pages/tools-by-category/index.tsx | 28 +++++++++++++++------------ src/pages/tools/audio/i18n/en.json | 4 +--- src/pages/tools/string/i18n/en.json | 10 ---------- src/tools/defineTool.tsx | 6 +++--- 5 files changed, 21 insertions(+), 32 deletions(-) diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 0cec8ab..09abe50 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -70,12 +70,9 @@ export type FullI18nKey = { }[I18nNamespaces]; i18n.use(Backend).use(initReactI18next).init({ - resources, lng: 'en', fallbackLng: 'en', - backend: locizeOptions, - saveMissing: true, // Send missing keys to Locize - updateMissing: true // Update keys in Locize + backend: locizeOptions }); export default i18n; diff --git a/src/pages/tools-by-category/index.tsx b/src/pages/tools-by-category/index.tsx index 947862e..399c815 100644 --- a/src/pages/tools-by-category/index.tsx +++ b/src/pages/tools-by-category/index.tsx @@ -2,8 +2,8 @@ import { Box, Divider, Stack, - TextField, styled, + TextField, useTheme } from '@mui/material'; import Grid from '@mui/material/Grid'; @@ -11,16 +11,16 @@ import Typography from '@mui/material/Typography'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { filterTools, getToolsByCategory } from '../../tools'; import Hero from 'components/Hero'; -import { capitalizeFirstLetter, getToolCategoryTitle } from '@utils/string'; +import { getToolCategoryTitle } from '@utils/string'; import { Icon } from '@iconify/react'; import { categoriesColors } from 'config/uiConfig'; import React, { useEffect } from 'react'; import IconButton from '@mui/material/IconButton'; -import { ArrowBack } from '@mui/icons-material'; -import BackButton from '@components/BackButton'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import SearchIcon from '@mui/icons-material/Search'; import { Helmet } from 'react-helmet'; +import { useTranslation } from 'react-i18next'; +import { I18nNamespaces } from '../../i18n'; const StyledLink = styled(Link)(({ theme }) => ({ '&:hover': { @@ -34,7 +34,15 @@ export default function ToolsByCategory() { const { categoryName } = useParams(); const [searchTerm, setSearchTerm] = React.useState(''); const rawTitle = getToolCategoryTitle(categoryName as string); - + const categoryTools = filterTools( + getToolsByCategory().find(({ type }) => type === categoryName)?.tools ?? [], + searchTerm + ); + const { t } = useTranslation( + categoryTools.length + ? (categoryTools[0].name.split(':')[0] as I18nNamespaces) + : 'translation' + ); useEffect(() => { if (mainContentRef.current) { mainContentRef.current.scrollIntoView({ behavior: 'smooth' }); @@ -82,11 +90,7 @@ export default function ToolsByCategory() { /> - {filterTools( - getToolsByCategory().find(({ type }) => type === categoryName) - ?.tools ?? [], - searchTerm - ).map((tool, index) => ( + {categoryTools.map((tool, index) => ( - {tool.name} + {t(tool.name)} - {tool.shortDescription} + {t(tool.shortDescription)} diff --git a/src/pages/tools/audio/i18n/en.json b/src/pages/tools/audio/i18n/en.json index f3caae7..3955ede 100644 --- a/src/pages/tools/audio/i18n/en.json +++ b/src/pages/tools/audio/i18n/en.json @@ -11,7 +11,6 @@ "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." }, - "title": "Extract audio", "shortDescription": "Extract audio from video files (MP4, MOV, etc.) to AAC, MP3, or WAV." }, "changeSpeed": { @@ -27,8 +26,7 @@ "toolInfo": { "title": "What is {{title}}?", "description": "" - }, - "title": "Change audio speed" + } }, "mergeAudio": { "title": "Merge Audio", diff --git a/src/pages/tools/string/i18n/en.json b/src/pages/tools/string/i18n/en.json index 627d845..a083424 100644 --- a/src/pages/tools/string/i18n/en.json +++ b/src/pages/tools/string/i18n/en.json @@ -4,7 +4,6 @@ "description": "Convert text to uppercase letters.", "inputTitle": "Input text", "resultTitle": "Uppercase text", - "title": "Uppercase", "shortDescription": "Convert text to uppercase" }, "base64": { @@ -19,7 +18,6 @@ "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": "Base64", "shortDescription": "Encode or decode data using Base64." }, "truncate": { @@ -45,7 +43,6 @@ "title": "Truncate text", "description": "Load your text in the input form on the left and you will automatically get truncated text on the right." }, - "title": "Truncate", "shortDescription": "Truncate text to a specified length" }, "quote": { @@ -63,7 +60,6 @@ "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": "Quote", "shortDescription": "Add quotes around text with various styles" }, "join": { @@ -83,7 +79,6 @@ "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": "Join", "shortDescription": "Join text elements with a specified separator" }, "rotate": { @@ -100,7 +95,6 @@ "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": "Rotate", "shortDescription": "Shift characters in text by position." }, "repeat": { @@ -118,7 +112,6 @@ "title": "Repeat text", "description": "This tool allows you to repeat a given text multiple times with an optional separator." }, - "title": "Repeat text", "shortDescription": "Repeat text multiple times" }, "rot13": { @@ -130,11 +123,9 @@ "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": "Rot13", "shortDescription": "Encode or decode text using ROT13 cipher." }, "toMorse": { - "title": "To Morse", "description": "Convert text to Morse code.", "resultTitle": "Morse code", "shortSignal": "Short Signal", @@ -165,7 +156,6 @@ "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." }, - "title": "Text Statistics", "shortDescription": "Get statistics about your text" }, "textReplacer": { diff --git a/src/tools/defineTool.tsx b/src/tools/defineTool.tsx index 0955200..83616ba 100644 --- a/src/tools/defineTool.tsx +++ b/src/tools/defineTool.tsx @@ -70,10 +70,10 @@ export const defineTool = ( return { type: basePath, path: `${basePath}/${path}`, - name, + name: i18n?.name || name, icon, - description, - shortDescription, + description: i18n?.description || description, + shortDescription: i18n?.shortDescription || shortDescription, keywords, component: () => { return ( From c64c29878ec33bbf6639605bc0af64a8f885bba0 Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Mon, 14 Jul 2025 13:55:01 +0100 Subject: [PATCH 12/29] chore: add i18n to meta script --- .idea/workspace.xml | 920 +++++------------- scripts/add-i18n-to-meta.js | 260 +++++ scripts/update-i18n-from-meta.js | 203 ++++ src/pages/tools/audio/i18n/en.json | 6 +- src/pages/tools/audio/merge-audio/meta.ts | 6 + src/pages/tools/audio/trim/meta.ts | 6 + .../tools/csv/csv-rows-to-columns/meta.ts | 6 + src/pages/tools/csv/csv-to-json/meta.ts | 5 + src/pages/tools/csv/csv-to-tsv/meta.ts | 6 + src/pages/tools/csv/csv-to-xml/meta.ts | 5 + src/pages/tools/csv/csv-to-yaml/meta.ts | 6 + src/pages/tools/csv/i18n/en.json | 21 +- .../tools/csv/insert-csv-columns/meta.ts | 5 + src/pages/tools/csv/swap-csv-columns/meta.ts | 6 + src/pages/tools/csv/transpose-csv/meta.ts | 6 + .../tools/image/generic/change-colors/meta.ts | 5 + .../image/generic/change-opacity/meta.ts | 5 + .../tools/image/generic/compress/meta.ts | 5 + .../image/generic/convert-to-jpg/meta.ts | 5 + .../image/generic/create-transparent/meta.ts | 5 + src/pages/tools/image/generic/crop/meta.ts | 5 + src/pages/tools/image/generic/editor/meta.ts | 5 + .../tools/image/generic/image-to-text/meta.ts | 5 + src/pages/tools/image/generic/qr-code/meta.ts | 5 + .../image/generic/remove-background/meta.ts | 5 + src/pages/tools/image/generic/resize/meta.ts | 5 + src/pages/tools/image/generic/rotate/meta.ts | 5 + src/pages/tools/image/i18n/en.json | 64 +- .../tools/image/png/compress-png/meta.ts | 5 + .../image/png/convert-jgp-to-png/meta.ts | 5 + src/pages/tools/pdf/editor/meta.ts | 5 + src/pages/tools/pdf/i18n/en.json | 9 +- src/pages/tools/pdf/pdf-to-png/meta.ts | 6 + src/pages/tools/pdf/rotate-pdf/meta.ts | 6 + src/pages/tools/string/i18n/en.json | 3 +- src/pages/tools/string/rot13/meta.ts | 5 + src/pages/tools/string/rotate/meta.ts | 5 + src/pages/tools/string/text-replacer/meta.ts | 5 + src/tools/defineTool.tsx | 2 +- 39 files changed, 941 insertions(+), 706 deletions(-) create mode 100644 scripts/add-i18n-to-meta.js create mode 100644 scripts/update-i18n-from-meta.js diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 648248c..68657e1 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,12 +4,45 @@ - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - { + "prStates": [ { - "id": { - "id": "PR_kwDOMJIfts51PkS9", - "number": 22 + "id": { + "id": "PR_kwDOMJIfts51PkS9", + "number": 22 }, - "lastSeen": 1741207144695 + "lastSeen": 1741207144695 }, { - "id": { - "id": "PR_kwDOMJIfts6NiNYl", - "number": 32 + "id": { + "id": "PR_kwDOMJIfts6NiNYl", + "number": 32 }, - "lastSeen": 1741209723869 + "lastSeen": 1741209723869 }, { - "id": { - "id": "PR_kwDOMJIfts6Nheyd", - "number": 31 + "id": { + "id": "PR_kwDOMJIfts6Nheyd", + "number": 31 }, - "lastSeen": 1741213371410 + "lastSeen": 1741213371410 }, { - "id": { - "id": "PR_kwDOMJIfts6NmRBs", - "number": 33 + "id": { + "id": "PR_kwDOMJIfts6NmRBs", + "number": 33 }, - "lastSeen": 1741282429036 + "lastSeen": 1741282429036 }, { - "id": { - "id": "PR_kwDOMJIfts5zyFTs", - "number": 15 + "id": { + "id": "PR_kwDOMJIfts5zyFTs", + "number": 15 }, - "lastSeen": 1741535540953 + "lastSeen": 1741535540953 }, { - "id": { - "id": "PR_kwDOMJIfts6QQB3c", - "number": 59 + "id": { + "id": "PR_kwDOMJIfts6QQB3c", + "number": 59 }, - "lastSeen": 1743018960900 + "lastSeen": 1743018960900 }, { - "id": { - "id": "PR_kwDOMJIfts6QMPEg", - "number": 58 + "id": { + "id": "PR_kwDOMJIfts6QMPEg", + "number": 58 }, - "lastSeen": 1743019452983 + "lastSeen": 1743019452983 }, { - "id": { - "id": "PR_kwDOMJIfts6QZvRI", - "number": 61 + "id": { + "id": "PR_kwDOMJIfts6QZvRI", + "number": 61 }, - "lastSeen": 1743103196866 + "lastSeen": 1743103196866 }, { - "id": { - "id": "PR_kwDOMJIfts6QqPrQ", - "number": 73 + "id": { + "id": "PR_kwDOMJIfts6QqPrQ", + "number": 73 }, - "lastSeen": 1743265865001 + "lastSeen": 1743265865001 }, { - "id": { - "id": "PR_kwDOMJIfts6Qp5nI", - "number": 72 + "id": { + "id": "PR_kwDOMJIfts6Qp5nI", + "number": 72 }, - "lastSeen": 1743338472110 + "lastSeen": 1743338472110 }, { - "id": { - "id": "PR_kwDOMJIfts6QsjlS", - "number": 76 + "id": { + "id": "PR_kwDOMJIfts6QsjlS", + "number": 76 }, - "lastSeen": 1743352150953 + "lastSeen": 1743352150953 }, { - "id": { - "id": "PR_kwDOMJIfts6Q0JBe", - "number": 82 + "id": { + "id": "PR_kwDOMJIfts6Q0JBe", + "number": 82 }, - "lastSeen": 1743470267269 + "lastSeen": 1743470267269 }, { - "id": { - "id": "PR_kwDOMJIfts6UE9-x", - "number": 102 + "id": { + "id": "PR_kwDOMJIfts6UE9-x", + "number": 102 }, - "lastSeen": 1747171977348 + "lastSeen": 1747171977348 }, { - "id": { - "id": "PR_kwDOMJIfts6XPua_", - "number": 117 + "id": { + "id": "PR_kwDOMJIfts6XPua_", + "number": 117 }, - "lastSeen": 1747929835864 + "lastSeen": 1747929835864 }, { - "id": { - "id": "PR_kwDOMJIfts6XY-mZ", - "number": 119 + "id": { + "id": "PR_kwDOMJIfts6XY-mZ", + "number": 119 }, - "lastSeen": 1748028108508 + "lastSeen": 1748028108508 }, { - "id": { - "id": "PR_kwDOMJIfts6Xdz4n", - "number": 120 + "id": { + "id": "PR_kwDOMJIfts6Xdz4n", + "number": 120 }, - "lastSeen": 1748282672214 + "lastSeen": 1748282672214 }, { - "id": { - "id": "PR_kwDOMJIfts6X_zxl", - "number": 131 + "id": { + "id": "PR_kwDOMJIfts6X_zxl", + "number": 131 }, - "lastSeen": 1748881279494 + "lastSeen": 1748881279494 }, { - "id": { - "id": "PR_kwDOMJIfts6bhieT", - "number": 152 + "id": { + "id": "PR_kwDOMJIfts6bhieT", + "number": 152 }, - "lastSeen": 1751848489082 + "lastSeen": 1751848489082 }, { - "id": { - "id": "PR_kwDOMJIfts6dOyRk", - "number": 154 + "id": { + "id": "PR_kwDOMJIfts6dOyRk", + "number": 154 }, - "lastSeen": 1751849436454 + "lastSeen": 1751849436454 }, { - "id": { - "id": "PR_kwDOMJIfts6cHjNi", - "number": 153 + "id": { + "id": "PR_kwDOMJIfts6cHjNi", + "number": 153 }, - "lastSeen": 1751849501498 + "lastSeen": 1751849501498 }, { - "id": { - "id": "PR_kwDOMJIfts6Zs1FN", - "number": 145 + "id": { + "id": "PR_kwDOMJIfts6Zs1FN", + "number": 145 }, - "lastSeen": 1751849770308 + "lastSeen": 1751849770308 }, { - "id": { - "id": "PR_kwDOMJIfts6bgKi9", - "number": 150 + "id": { + "id": "PR_kwDOMJIfts6bgKi9", + "number": 150 }, - "lastSeen": 1751850367300 + "lastSeen": 1751850367300 }, { - "id": { - "id": "PR_kwDOMJIfts6eUKC-", - "number": 176 + "id": { + "id": "PR_kwDOMJIfts6eUKC-", + "number": 176 }, - "lastSeen": 1752158748013 + "lastSeen": 1752158748013 }, { - "id": { - "id": "PR_kwDOMJIfts6eqzP7", - "number": 190 + "id": { + "id": "PR_kwDOMJIfts6eqzP7", + "number": 190 }, - "lastSeen": 1752404173008 + "lastSeen": 1752404173008 } ] -}]]> +} { "selectedUrlAndAccountId": { "url": "https://github.com/iib0011/omni-tools.git", @@ -246,56 +279,59 @@ - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true", - "Docker.Dockerfile build.executor": "Run", - "Docker.Dockerfile.executor": "Run", - "Playwright.Create transparent PNG.should make png color transparent.executor": "Run", - "Playwright.JoinText Component.executor": "Run", - "Playwright.JoinText Component.should merge text pieces with specified join character.executor": "Run", - "RunOnceActivity.OpenProjectViewOnStart": "true", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "Vitest.compute function (1).executor": "Run", - "Vitest.compute function.executor": "Run", - "Vitest.mergeText.executor": "Run", - "Vitest.mergeText.should merge lines and preserve blank lines when deleteBlankLines is false.executor": "Run", - "Vitest.mergeText.should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false.executor": "Run", - "Vitest.parsePageRanges.executor": "Run", - "Vitest.removeDuplicateLines function.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.executor": "Run", - "Vitest.removeDuplicateLines function.newlines option.should filter newlines when newlines is set to filter.executor": "Run", - "Vitest.replaceText function (regexp mode).should return the original text when passed an invalid regexp.executor": "Run", - "Vitest.replaceText function.executor": "Run", - "Vitest.timeBetweenDates.executor": "Run", - "git-widget-placeholder": "#190 on fork/AshAnand34/en-hi-translation", - "ignore.virus.scanning.warn.message": "true", - "kotlin-language-version-configured": "true", - "last_opened_file_path": "C:/Users/Ibrahima/IdeaProjects/omni-tools/src", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "npm.build.executor": "Run", - "npm.dev.executor": "Run", - "npm.lint.executor": "Run", - "npm.prebuild.executor": "Run", - "npm.script:create:tool.executor": "Run", - "npm.test.executor": "Run", - "npm.test:e2e.executor": "Run", - "npm.test:e2e:run.executor": "Run", - "prettierjs.PrettierConfiguration.Package": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\prettier", - "project.structure.last.edited": "Problems", - "project.structure.proportion": "0.0", - "project.structure.side.proportion": "0.2", - "settings.editor.selected.configurable": "refactai_advanced_settings", - "ts.external.directory.path": "C:\\Users\\Ibrahima\\IdeaProjects\\omni-tools\\node_modules\\typescript\\lib", - "vue.rearranger.settings.migration": "true" + +}]]> - - - - - - - - - - - - - - + @@ -349,17 +372,13 @@ - - - - - - - - - - - + + + + + + + @@ -375,33 +394,25 @@