feat: password generator to test translation

This commit is contained in:
Ibrahima G. Coulibaly
2025-07-14 18:33:59 +01:00
parent 3b5f852287
commit 4441f987d2
8 changed files with 407 additions and 129 deletions

143
.idea/workspace.xml generated
View File

@@ -4,119 +4,12 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: saveMissing">
<change afterPath="$PROJECT_DIR$/.env.example" afterDir="false" />
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="fix: translation related behaviors">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/Hero.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Hero.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/ToolLayout.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/ToolLayout.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/i18n/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/i18n/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools-by-category/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/audio/change-speed/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/audio/change-speed/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/audio/extract-audio/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/audio/extract-audio/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/audio/merge-audio/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/audio/merge-audio/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/audio/trim/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/audio/trim/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/change-csv-separator/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/change-csv-separator/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/csv-rows-to-columns/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/csv-rows-to-columns/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-json/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-json/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-tsv/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-tsv/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-xml/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-xml/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-yaml/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/csv-to-yaml/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/find-incomplete-csv-records/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/find-incomplete-csv-records/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/insert-csv-columns/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/insert-csv-columns/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/swap-csv-columns/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/swap-csv-columns/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/csv/transpose-csv/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/csv/transpose-csv/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/change-colors/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-colors/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/change-opacity/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/change-opacity/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/compress/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/compress/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/convert-to-jpg/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/convert-to-jpg/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/create-transparent/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/create-transparent/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/crop/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/crop/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/editor/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/editor/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/image-to-text/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/image-to-text/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/qr-code/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/qr-code/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/remove-background/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/remove-background/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/resize/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/resize/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/generic/rotate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/generic/rotate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/compress-png/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/compress-png/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/convert-jgp-to-png/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/convert-jgp-to-png/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/escape-json/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/escape-json/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/json-to-xml/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/json-to-xml/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/minify/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/minify/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/prettify/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/prettify/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/stringify/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/stringify/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/tsv-to-json/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/tsv-to-json/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/json/validateJson/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/json/validateJson/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/duplicate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/duplicate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/find-most-popular/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/find-most-popular/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/find-unique/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/find-unique/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/group/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/group/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/reverse/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/reverse/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/rotate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/rotate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/shuffle/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/shuffle/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/sort/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/sort/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/truncate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/truncate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/unwrap/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/unwrap/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/wrap/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/wrap/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/arithmetic-sequence/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/arithmetic-sequence/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/generate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/ohmsLaw.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/ohmsLaw.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/slackline.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/slackline.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/sphereArea.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/sphereArea.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/sphereVolume.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/sphereVolume.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/voltageDropInWire.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/data/voltageDropInWire.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/generic-calc/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/number/sum/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/number/sum/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/compress-pdf/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/editor/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/editor/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/merge-pdf/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/merge-pdf/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/pdf-to-epub/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/pdf-to-epub/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/pdf-to-png/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/pdf-to-png/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/protect-pdf/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/rotate-pdf/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/rotate-pdf/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/pdf/split-pdf/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/base64/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/base64/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/censor/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/censor/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/create-palindrome/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/create-palindrome/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/extract-substring/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/extract-substring/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/join/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/join/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/palindrome/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/palindrome/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/quote/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/quote/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/randomize-case/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/randomize-case/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/remove-duplicate-lines/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/remove-duplicate-lines/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/repeat/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/repeat/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/reverse/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/reverse/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/rot13/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/rot13/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/rotate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/rotate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/split/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/split/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/statistic/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/statistic/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/text-replacer/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/text-replacer/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/to-morse/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/to-morse/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/truncate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/truncate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/uppercase/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/uppercase/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/check-leap-years/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/check-leap-years/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/convert-days-to-hours/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/convert-days-to-hours/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/convert-hours-to-days/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/convert-hours-to-days/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/convert-seconds-to-time/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/convert-seconds-to-time/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/convert-time-to-seconds/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/convert-time-to-seconds/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/crontab-guru/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/crontab-guru/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/time-between-dates/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/time-between-dates/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/time/truncate-clock-time/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/time/truncate-clock-time/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/change-speed/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/change-speed/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/compress/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/compress/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/crop-video/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/crop-video/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/flip/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/flip/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/gif/change-speed/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/loop/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/loop/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/rotate/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/rotate/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/trim/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/trim/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/video/video-to-gif/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/video/video-to-gif/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/xml-beautifier/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/xml/xml-beautifier/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/xml/xml-validator/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/xml/xml-validator/meta.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/i18n/en.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/i18n/en.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/i18n/hi.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/i18n/hi.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tools/defineTool.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/defineTool.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/tools/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/tools/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/utils/string.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/string.ts" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -487,7 +380,7 @@
</scripts>
<node-interpreter value="project" />
<envs>
<env name="LOCIZE_API_KEY" value="a2ac4dc2-d10le-4d35-bcf7-92db87381711" />
<env name="LOCIZE_API_KEY" value="a2ac4dc2-d10e-4d35-bcf7-92db87381711" />
</envs>
<method v="2" />
</configuration>
@@ -617,15 +510,7 @@
<workItem from="1752157409587" duration="2415000" />
<workItem from="1752403829295" duration="13253000" />
<workItem from="1752493585622" duration="11629000" />
<workItem from="1752507105323" duration="4936000" />
</task>
<task id="LOCAL-00181" summary="fix: stars button width for 1k+ 😊">
<option name="closed" value="true" />
<created>1743470832619</created>
<option name="number" value="00181" />
<option name="presentableId" value="LOCAL-00181" />
<option name="project" value="LOCAL" />
<updated>1743470832619</updated>
<workItem from="1752507105323" duration="6496000" />
</task>
<task id="LOCAL-00182" summary="feat: compress pdf">
<option name="closed" value="true" />
@@ -1011,7 +896,15 @@
<option name="project" value="LOCAL" />
<updated>1752505593881</updated>
</task>
<option name="localTasksCounter" value="230" />
<task id="LOCAL-00230" summary="fix: translation related behaviors">
<option name="closed" value="true" />
<created>1752512678963</created>
<option name="number" value="00230" />
<option name="presentableId" value="LOCAL-00230" />
<option name="project" value="LOCAL" />
<updated>1752512678963</updated>
</task>
<option name="localTasksCounter" value="231" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -1058,8 +951,6 @@
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" />
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
<MESSAGE value="chore: rename from Omni Tools to OmniTools" />
<MESSAGE value="fix: tools by category page title" />
<MESSAGE value="chore: use scrollY" />
<MESSAGE value="chore: remove flip x and y" />
<MESSAGE value="chore: new logo and font" />
@@ -1083,7 +974,9 @@
<MESSAGE value="fix: tsc" />
<MESSAGE value="chore: remove unnecessary" />
<MESSAGE value="chore: saveMissing" />
<option name="LAST_COMMIT_MESSAGE" value="chore: saveMissing" />
<MESSAGE value="fix: translations" />
<MESSAGE value="fix: translation related behaviors" />
<option name="LAST_COMMIT_MESSAGE" value="fix: translation related behaviors" />
</component>
<component name="VgoProject">
<integration-enabled>false</integration-enabled>

View File

@@ -17,6 +17,7 @@ import { tool as stringTruncate } from './truncate/meta';
import { tool as stringBase64 } from './base64/meta';
import { tool as stringStatistic } from './statistic/meta';
import { tool as stringCensor } from './censor/meta';
import { tool as stringPasswordGenerator } from './password-generator/meta';
export const stringTools = [
stringSplit,
@@ -37,5 +38,6 @@ export const stringTools = [
stringRot13,
stringBase64,
stringStatistic,
stringCensor
stringCensor,
stringPasswordGenerator
];

View File

@@ -0,0 +1,165 @@
import React, { useState } from 'react';
import { Box, Checkbox, FormControlLabel, FormGroup } from '@mui/material';
import { generatePassword } from './service';
import { initialValues, InitialValuesType } from './initialValues';
import ToolContent from '@components/ToolContent';
import ToolTextResult from '@components/result/ToolTextResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { ToolComponentProps } from '@tools/defineTool';
import { GetGroupsType } from '@components/options/ToolOptions';
import { CardExampleType } from '@components/examples/ToolExamples';
import { useTranslation } from 'react-i18next';
const exampleCards: CardExampleType<InitialValuesType>[] = [
{
title: 'Strong Password (12 characters)',
description:
'Generate a secure password with all character types including symbols.',
sampleText: '',
sampleResult: 'A7#mK9$pL2@x',
sampleOptions: {
length: '12',
includeLowercase: true,
includeUppercase: true,
includeNumbers: true,
includeSymbols: true,
avoidAmbiguous: false
}
},
{
title: 'Simple Password (8 characters)',
description: 'Generate a basic password with letters and numbers only.',
sampleText: '',
sampleResult: 'Ab3mK9pL',
sampleOptions: {
length: '8',
includeLowercase: true,
includeUppercase: true,
includeNumbers: true,
includeSymbols: false,
avoidAmbiguous: false
}
},
{
title: 'Clear Password (No ambiguous)',
description:
'Generate a password without ambiguous characters (i, I, l, 0, O).',
sampleText: '',
sampleResult: 'A7#mK9$pL2@x',
sampleOptions: {
length: '12',
includeLowercase: true,
includeUppercase: true,
includeNumbers: true,
includeSymbols: true,
avoidAmbiguous: true
}
}
];
export default function PasswordGenerator({ title }: ToolComponentProps) {
const { t } = useTranslation('string');
const [result, setResult] = useState<string>('');
function compute(values: InitialValuesType) {
setResult(generatePassword(values));
}
const getGroups: GetGroupsType<InitialValuesType> = ({
values,
updateField
}) => [
{
title: t('passwordGenerator.optionsTitle'),
component: (
<Box sx={{ display: 'flex', gap: 2, flexDirection: 'column' }}>
<TextFieldWithDesc
description={t('passwordGenerator.lengthDesc')}
placeholder={t('passwordGenerator.lengthPlaceholder')}
value={values.length}
onOwnChange={(val) => updateField('length', val)}
type="number"
/>
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={values.includeLowercase}
onChange={(e) =>
updateField('includeLowercase', e.target.checked)
}
/>
}
label={t('passwordGenerator.includeLowercase')}
/>
<FormControlLabel
control={
<Checkbox
checked={values.includeUppercase}
onChange={(e) =>
updateField('includeUppercase', e.target.checked)
}
/>
}
label={t('passwordGenerator.includeUppercase')}
/>
<FormControlLabel
control={
<Checkbox
checked={values.includeNumbers}
onChange={(e) =>
updateField('includeNumbers', e.target.checked)
}
/>
}
label={t('passwordGenerator.includeNumbers')}
/>
<FormControlLabel
control={
<Checkbox
checked={values.includeSymbols}
onChange={(e) =>
updateField('includeSymbols', e.target.checked)
}
/>
}
label={t('passwordGenerator.includeSymbols')}
/>
<FormControlLabel
control={
<Checkbox
checked={values.avoidAmbiguous}
onChange={(e) =>
updateField('avoidAmbiguous', e.target.checked)
}
/>
}
label={t('passwordGenerator.avoidAmbiguous')}
/>
</FormGroup>
</Box>
)
}
];
return (
<ToolContent
title={title}
initialValues={initialValues}
getGroups={getGroups}
compute={compute}
resultComponent={
<ToolTextResult
title={t('passwordGenerator.resultTitle')}
value={result}
/>
}
toolInfo={{
title: t('passwordGenerator.toolInfo.title'),
description: t('passwordGenerator.toolInfo.description')
}}
exampleCards={exampleCards}
/>
);
}

View File

@@ -0,0 +1,17 @@
export type InitialValuesType = {
length: string; // user enters a number here
includeLowercase: boolean;
includeUppercase: boolean;
includeNumbers: boolean;
includeSymbols: boolean;
avoidAmbiguous: boolean;
};
export const initialValues: InitialValuesType = {
length: '12',
includeLowercase: true,
includeUppercase: true,
includeNumbers: true,
includeSymbols: true,
avoidAmbiguous: false
};

View File

@@ -0,0 +1,14 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
export const tool = defineTool('string', {
path: 'password-generator',
icon: 'material-symbols:key',
keywords: ['password', 'generator', 'random', 'secure'],
component: lazy(() => import('./index')),
i18n: {
name: 'string:passwordGenerator.title',
description: 'string:passwordGenerator.description',
shortDescription: 'string:passwordGenerator.shortDescription'
}
});

View File

@@ -0,0 +1,143 @@
import { describe, expect, it } from 'vitest';
import { generatePassword } from './service';
import { initialValues } from './initialValues';
describe('generatePassword', () => {
it('should generate a password with the specified length', () => {
const options = { ...initialValues, length: '10' };
const result = generatePassword(options);
expect(result).toHaveLength(10);
});
it('should return empty string for invalid length', () => {
const options = { ...initialValues, length: '0' };
const result = generatePassword(options);
expect(result).toBe('');
});
it('should return empty string for non-numeric length', () => {
const options = { ...initialValues, length: 'abc' };
const result = generatePassword(options);
expect(result).toBe('');
});
it('should return empty string when no character types are selected', () => {
const options = {
...initialValues,
includeLowercase: false,
includeUppercase: false,
includeNumbers: false,
includeSymbols: false
};
const result = generatePassword(options);
expect(result).toBe('');
});
it('should only include lowercase letters when only lowercase is selected', () => {
const options = {
...initialValues,
length: '20',
includeLowercase: true,
includeUppercase: false,
includeNumbers: false,
includeSymbols: false
};
const result = generatePassword(options);
expect(result).toMatch(/^[a-z]+$/);
expect(result).toHaveLength(20);
});
it('should only include uppercase letters when only uppercase is selected', () => {
const options = {
...initialValues,
length: '15',
includeLowercase: false,
includeUppercase: true,
includeNumbers: false,
includeSymbols: false
};
const result = generatePassword(options);
expect(result).toMatch(/^[A-Z]+$/);
expect(result).toHaveLength(15);
});
it('should only include numbers when only numbers is selected', () => {
const options = {
...initialValues,
length: '8',
includeLowercase: false,
includeUppercase: false,
includeNumbers: true,
includeSymbols: false
};
const result = generatePassword(options);
expect(result).toMatch(/^[0-9]+$/);
expect(result).toHaveLength(8);
});
it('should include mixed character types when multiple are selected', () => {
const options = {
...initialValues,
length: '100', // larger sample for better testing
includeLowercase: true,
includeUppercase: true,
includeNumbers: true,
includeSymbols: false
};
const result = generatePassword(options);
expect(result).toMatch(/^[a-zA-Z0-9]+$/);
expect(result).toHaveLength(100);
});
it('should exclude ambiguous characters when avoidAmbiguous is true', () => {
const options = {
...initialValues,
length: '50',
avoidAmbiguous: true
};
const result = generatePassword(options);
expect(result).not.toMatch(/[iIl0O]/);
expect(result).toHaveLength(50);
});
it('should include symbols when includeSymbols is true', () => {
const options = {
...initialValues,
length: '30',
includeLowercase: false,
includeUppercase: false,
includeNumbers: false,
includeSymbols: true
};
const result = generatePassword(options);
expect(result).toMatch(/^[!@#$%^&*()_+~`|}{[\]:;?><,./-=]+$/);
expect(result).toHaveLength(30);
});
it('should exclude ambiguous characters from symbols too', () => {
const options = {
...initialValues,
length: '50',
includeLowercase: false,
includeUppercase: false,
includeNumbers: true,
includeSymbols: true,
avoidAmbiguous: true
};
const result = generatePassword(options);
expect(result).not.toMatch(/[iIl0O]/);
expect(result).toHaveLength(50);
});
it('should handle edge case with very short length', () => {
const options = { ...initialValues, length: '1' };
const result = generatePassword(options);
expect(result).toHaveLength(1);
});
it('should handle negative length', () => {
const options = { ...initialValues, length: '-5' };
const result = generatePassword(options);
expect(result).toBe('');
});
});

View File

@@ -0,0 +1,38 @@
import type { InitialValuesType } from './initialValues';
export function generatePassword(options: InitialValuesType): string {
const length = parseInt(options.length || '', 10);
if (isNaN(length) || length <= 0) {
return '';
}
let charset = '';
const lower = 'abcdefghijklmnopqrstuvwxyz';
const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const numbers = '0123456789';
const symbols = '!@#$%^&*()_+~`|}{[]:;?><,./-=';
if (options.includeLowercase) charset += lower;
if (options.includeUppercase) charset += upper;
if (options.includeNumbers) charset += numbers;
if (options.includeSymbols) charset += symbols;
if (options.avoidAmbiguous) {
// ambiguous set = i, I, l, 0, O
const ambig = new Set(['i', 'I', 'l', '0', 'O']);
charset = Array.from(charset)
.filter((c) => !ambig.has(c))
.join('');
}
if (!charset) {
return ''; // nothing to pick from
}
let pwd = '';
for (let i = 0; i < length; i++) {
const idx = Math.floor(Math.random() * charset.length);
pwd += charset[idx];
}
return pwd;
}

View File

@@ -2,7 +2,7 @@ import ToolLayout from '../components/ToolLayout';
import React, { JSXElementConstructor, LazyExoticComponent } from 'react';
import { IconifyIcon } from '@iconify/react';
import { FullI18nKey } from '../i18n';
import { ParseKeys } from 'i18next';
import { useTranslation } from 'react-i18next';
export interface ToolMeta {
path: string;
@@ -62,10 +62,16 @@ export const defineTool = (
description: i18n.description,
shortDescription: i18n.shortDescription,
keywords,
component: () => {
component: function ToolComponent() {
const { t } = useTranslation();
return (
<ToolLayout icon={icon} type={basePath} i18n={i18n}>
<Component title={i18n.name} longDescription={i18n.longDescription} />
<Component
title={t(i18n.name)}
longDescription={
i18n.longDescription ? t(i18n.longDescription) : undefined
}
/>
</ToolLayout>
);
}