+      
+        Preview image
+      
       
         Download file
       
diff --git a/src/tools/base64-file-converter/index.ts b/src/tools/base64-file-converter/index.ts
index c27e34ed..4d94402b 100644
--- a/src/tools/base64-file-converter/index.ts
+++ b/src/tools/base64-file-converter/index.ts
@@ -1,10 +1,11 @@
 import { FileDigit } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Base64 file converter',
+  name: translate('tools.base64-file-converter.title'),
   path: '/base64-file-converter',
-  description: 'Convert string, files or images into a it\'s base64 representation.',
+  description: translate('tools.base64-file-converter.description'),
   keywords: ['base64', 'converter', 'upload', 'image', 'file', 'conversion', 'web', 'data', 'format'],
   component: () => import('./base64-file-converter.vue'),
   icon: FileDigit,
diff --git a/src/tools/base64-string-converter/index.ts b/src/tools/base64-string-converter/index.ts
index 0dd9bee2..e51d54df 100644
--- a/src/tools/base64-string-converter/index.ts
+++ b/src/tools/base64-string-converter/index.ts
@@ -1,10 +1,11 @@
 import { FileDigit } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Base64 string encoder/decoder',
+  name: translate('tools.base64-string-converter.title'),
   path: '/base64-string-converter',
-  description: 'Simply encode and decode string into a their base64 representation.',
+  description: translate('tools.base64-string-converter.description'),
   keywords: ['base64', 'converter', 'conversion', 'web', 'data', 'format', 'atob', 'btoa'],
   component: () => import('./base64-string-converter.vue'),
   icon: FileDigit,
diff --git a/src/tools/basic-auth-generator/index.ts b/src/tools/basic-auth-generator/index.ts
index 3138b504..eff6eae3 100644
--- a/src/tools/basic-auth-generator/index.ts
+++ b/src/tools/basic-auth-generator/index.ts
@@ -1,10 +1,11 @@
 import { PasswordRound } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Basic auth generator',
+  name: translate('tools.basic-auth-generator.title'),
   path: '/basic-auth-generator',
-  description: 'Generate a base64 basic auth header from an username and a password.',
+  description: translate('tools.basic-auth-generator.description'),
   keywords: [
     'basic',
     'auth',
diff --git a/src/tools/bcrypt/bcrypt.vue b/src/tools/bcrypt/bcrypt.vue
index c28c20bf..d4881299 100644
--- a/src/tools/bcrypt/bcrypt.vue
+++ b/src/tools/bcrypt/bcrypt.vue
@@ -28,7 +28,7 @@ const compareMatch = computed(() => compareSync(compareString.value, compareHash
       mb-2
     />
     
-      
+      
     
 
     
diff --git a/src/tools/bcrypt/index.ts b/src/tools/bcrypt/index.ts
index f70a3a60..9c80c694 100644
--- a/src/tools/bcrypt/index.ts
+++ b/src/tools/bcrypt/index.ts
@@ -1,11 +1,11 @@
 import { LockSquare } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Bcrypt',
+  name: translate('tools.bcrypt.title'),
   path: '/bcrypt',
-  description:
-    'Hash and compare text string using bcrypt. Bcrypt is a password-hashing function based on the Blowfish cipher.',
+  description: translate('tools.bcrypt.description'),
   keywords: ['bcrypt', 'hash', 'compare', 'password', 'salt', 'round', 'storage', 'crypto'],
   component: () => import('./bcrypt.vue'),
   icon: LockSquare,
diff --git a/src/tools/benchmark-builder/index.ts b/src/tools/benchmark-builder/index.ts
index 51eb8058..426d287a 100644
--- a/src/tools/benchmark-builder/index.ts
+++ b/src/tools/benchmark-builder/index.ts
@@ -1,10 +1,11 @@
 import { SpeedFilled } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Benchmark builder',
+  name: translate('tools.benchmark-builder.title'),
   path: '/benchmark-builder',
-  description: 'Easily compare execution time of tasks with this very simple online benchmark builder.',
+  description: translate('tools.benchmark-builder.description'),
   keywords: ['benchmark', 'builder', 'execution', 'duration', 'mean', 'variance'],
   component: () => import('./benchmark-builder.vue'),
   icon: SpeedFilled,
diff --git a/src/tools/bip39-generator/index.ts b/src/tools/bip39-generator/index.ts
index f649e188..40582da4 100644
--- a/src/tools/bip39-generator/index.ts
+++ b/src/tools/bip39-generator/index.ts
@@ -1,10 +1,11 @@
 import { AlignJustified } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'BIP39 passphrase generator',
+  name: translate('tools.bip39-generator.title'),
   path: '/bip39-generator',
-  description: 'Generate BIP39 passphrase from existing or random mnemonic, or get the mnemonic from the passphrase.',
+  description: translate('tools.bip39-generator.description'),
   keywords: ['BIP39', 'passphrase', 'generator', 'mnemonic', 'entropy'],
   component: () => import('./bip39-generator.vue'),
   icon: AlignJustified,
diff --git a/src/tools/camera-recorder/index.ts b/src/tools/camera-recorder/index.ts
index 3c5d11bd..5cda41f3 100644
--- a/src/tools/camera-recorder/index.ts
+++ b/src/tools/camera-recorder/index.ts
@@ -1,10 +1,11 @@
 import { Camera } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Camera recorder',
+  name: translate('tools.camera-recorder.title'),
   path: '/camera-recorder',
-  description: 'Take a picture or record a video from your webcam or camera.',
+  description: translate('tools.camera-recorder.description'),
   keywords: ['camera', 'recoder'],
   component: () => import('./camera-recorder.vue'),
   icon: Camera,
diff --git a/src/tools/case-converter/index.ts b/src/tools/case-converter/index.ts
index 710a03f2..14d7ec12 100644
--- a/src/tools/case-converter/index.ts
+++ b/src/tools/case-converter/index.ts
@@ -1,10 +1,11 @@
 import { LetterCaseToggle } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Case converter',
+  name: translate('tools.case-converter.title'),
   path: '/case-converter',
-  description: 'Change the case of a string and chose between different formats',
+  description: translate('tools.case-converter.description'),
   keywords: [
     'case',
     'converter',
diff --git a/src/tools/chmod-calculator/index.ts b/src/tools/chmod-calculator/index.ts
index 7d299e42..e6b39df8 100644
--- a/src/tools/chmod-calculator/index.ts
+++ b/src/tools/chmod-calculator/index.ts
@@ -1,10 +1,11 @@
 import { FileInvoice } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Chmod calculator',
+  name: translate('tools.chmod-calculator.title'),
   path: '/chmod-calculator',
-  description: 'Compute your chmod permissions and commands with this online chmod calculator.',
+  description: translate('tools.chmod-calculator.description'),
   keywords: [
     'chmod',
     'calculator',
diff --git a/src/tools/chronometer/index.ts b/src/tools/chronometer/index.ts
index 424d03dc..d7d8196a 100644
--- a/src/tools/chronometer/index.ts
+++ b/src/tools/chronometer/index.ts
@@ -1,10 +1,11 @@
 import { TimerOutlined } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Chronometer',
+  name: translate('tools.chronometer.title'),
   path: '/chronometer',
-  description: 'Monitor the duration of a thing. Basically a chronometer with simple chronometer features.',
+  description: translate('tools.chronometer.description'),
   keywords: ['chronometer', 'time', 'lap', 'duration', 'measure', 'pause', 'resume', 'stopwatch'],
   component: () => import('./chronometer.vue'),
   icon: TimerOutlined,
diff --git a/src/tools/color-converter/index.ts b/src/tools/color-converter/index.ts
index c82689cc..9a295e2b 100644
--- a/src/tools/color-converter/index.ts
+++ b/src/tools/color-converter/index.ts
@@ -1,10 +1,11 @@
 import { Palette } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Color converter',
+  name: translate('tools.color-converter.title'),
   path: '/color-converter',
-  description: 'Convert color between the different formats (hex, rgb, hsl and css name)',
+  description: translate('tools.color-converter.description'),
   keywords: ['color', 'converter'],
   component: () => import('./color-converter.vue'),
   icon: Palette,
diff --git a/src/tools/crontab-generator/index.ts b/src/tools/crontab-generator/index.ts
index 49b28389..429d6e14 100644
--- a/src/tools/crontab-generator/index.ts
+++ b/src/tools/crontab-generator/index.ts
@@ -1,10 +1,11 @@
 import { Alarm } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Crontab generator',
+  name: translate('tools.crontab-generator.title'),
   path: '/crontab-generator',
-  description: 'Validate and generate crontab and get the human readable description of the cron schedule.',
+  description: translate('tools.crontab-generator.description'),
   keywords: [
     'crontab',
     'generator',
diff --git a/src/tools/date-time-converter/index.ts b/src/tools/date-time-converter/index.ts
index 4bc66bf1..b0413fc0 100644
--- a/src/tools/date-time-converter/index.ts
+++ b/src/tools/date-time-converter/index.ts
@@ -1,10 +1,11 @@
 import { Calendar } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Date-time converter',
+  name: translate('tools.date-converter.title'),
   path: '/date-converter',
-  description: 'Convert date and time into the various different formats',
+  description: translate('tools.date-converter.description'),
   keywords: ['date', 'time', 'converter', 'iso', 'utc', 'timezone', 'year', 'month', 'day', 'minute', 'seconde'],
   component: () => import('./date-time-converter.vue'),
   icon: Calendar,
diff --git a/src/tools/device-information/index.ts b/src/tools/device-information/index.ts
index e55ae28c..44d91598 100644
--- a/src/tools/device-information/index.ts
+++ b/src/tools/device-information/index.ts
@@ -1,10 +1,11 @@
 import { DeviceDesktop } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Device information',
+  name: translate('tools.device-information.title'),
   path: '/device-information',
-  description: 'Get information about your current device (screen size, pixel-ratio, user agent, ...)',
+  description: translate('tools.device-information.description'),
   keywords: [
     'device',
     'information',
diff --git a/src/tools/docker-run-to-docker-compose-converter/index.ts b/src/tools/docker-run-to-docker-compose-converter/index.ts
index d9c1437f..0ecc4b0b 100644
--- a/src/tools/docker-run-to-docker-compose-converter/index.ts
+++ b/src/tools/docker-run-to-docker-compose-converter/index.ts
@@ -1,10 +1,11 @@
 import { BrandDocker } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Docker run to Docker compose converter',
+  name: translate('tools.docker-run-to-docker-compose-converter.title'),
   path: '/docker-run-to-docker-compose-converter',
-  description: 'Turns docker run commands into docker-compose files!',
+  description: translate('tools.docker-run-to-docker-compose-converter.description'),
   keywords: ['docker', 'run', 'compose', 'yaml', 'yml', 'convert', 'deamon'],
   component: () => import('./docker-run-to-docker-compose-converter.vue'),
   icon: BrandDocker,
diff --git a/src/tools/email-normalizer/email-normalizer.vue b/src/tools/email-normalizer/email-normalizer.vue
new file mode 100644
index 00000000..eae97c4e
--- /dev/null
+++ b/src/tools/email-normalizer/email-normalizer.vue
@@ -0,0 +1,65 @@
+
+
+
+  
+    
+      Raw emails to normalize:
+    
+    
+
+    
+      Normalized emails:
+    
+    
+    
+      
+        Clear emails
+      
+      
+        Copy normalized emails
+      
+    
+  
diff --git a/src/tools/email-normalizer/index.ts b/src/tools/email-normalizer/index.ts
new file mode 100644
index 00000000..299a30f7
--- /dev/null
+++ b/src/tools/email-normalizer/index.ts
@@ -0,0 +1,12 @@
+import { Mail } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+  name: 'Email normalizer',
+  path: '/email-normalizer',
+  description: 'Normalize email addresses to a standard format for easier comparison. Useful for deduplication and data cleaning.',
+  keywords: ['email', 'normalizer'],
+  component: () => import('./email-normalizer.vue'),
+  icon: Mail,
+  createdAt: new Date('2024-08-15'),
+});
diff --git a/src/tools/emoji-picker/emoji-picker.vue b/src/tools/emoji-picker/emoji-picker.vue
index 750695f5..a12b10c2 100644
--- a/src/tools/emoji-picker/emoji-picker.vue
+++ b/src/tools/emoji-picker/emoji-picker.vue
@@ -4,6 +4,7 @@ import emojiKeywords from 'emojilib';
 import _ from 'lodash';
 import type { EmojiInfo } from './emoji.types';
 import { useFuzzySearch } from '@/composable/fuzzySearch';
+import useDebouncedRef from '@/composable/debouncedref';
 
 const escapeUnicode = ({ emoji }: { emoji: string }) => emoji.split('').map(unit => `\\u${unit.charCodeAt(0).toString(16).padStart(4, '0')}`).join('');
 const getEmojiCodePoints = ({ emoji }: { emoji: string }) => emoji.codePointAt(0) ? `0x${emoji.codePointAt(0)?.toString(16)}` : undefined;
@@ -23,7 +24,7 @@ const emojisGroups: { emojiInfos: EmojiInfo[]; group: string }[] = _
   .map((emojiInfos, group) => ({ group, emojiInfos }))
   .value();
 
-const searchQuery = ref('');
+const searchQuery = useDebouncedRef('', 500);
 
 const { searchResult } = useFuzzySearch({
   search: searchQuery,
diff --git a/src/tools/emoji-picker/index.ts b/src/tools/emoji-picker/index.ts
index ef01b2de..3a28cf0f 100644
--- a/src/tools/emoji-picker/index.ts
+++ b/src/tools/emoji-picker/index.ts
@@ -1,10 +1,11 @@
 import { MoodSmile } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Emoji picker',
+  name: translate('tools.emoji-picker.title'),
   path: '/emoji-picker',
-  description: 'Copy and paste emojis easily and get the unicode and code points value of each emoji.',
+  description: translate('tools.emoji-picker.description'),
   keywords: ['emoji', 'picker', 'unicode', 'copy', 'paste'],
   component: () => import('./emoji-picker.vue'),
   icon: MoodSmile,
diff --git a/src/tools/encryption/index.ts b/src/tools/encryption/index.ts
index 9a95f4bc..c8dd85db 100644
--- a/src/tools/encryption/index.ts
+++ b/src/tools/encryption/index.ts
@@ -1,10 +1,11 @@
 import { Lock } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Encrypt / decrypt text',
+  name: translate('tools.encryption.title'),
   path: '/encryption',
-  description: 'Encrypt and decrypt text clear text using crypto algorithm like AES, TripleDES, Rabbit or RC4.',
+  description: translate('tools.encryption.description'),
   keywords: ['cypher', 'encipher', 'text', 'AES', 'TripleDES', 'Rabbit', 'RC4'],
   component: () => import('./encryption.vue'),
   icon: Lock,
diff --git a/src/tools/eta-calculator/index.ts b/src/tools/eta-calculator/index.ts
index abda2870..5016ab66 100644
--- a/src/tools/eta-calculator/index.ts
+++ b/src/tools/eta-calculator/index.ts
@@ -1,11 +1,11 @@
 import { Hourglass } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'ETA calculator',
+  name: translate('tools.eta-calculator.title'),
   path: '/eta-calculator',
-  description:
-    'An ETA (Estimated Time of Arrival) calculator to know the approximate end time of a task, for example the moment of ending of a download.',
+  description: translate('tools.eta-calculator.description'),
   keywords: ['eta', 'calculator', 'estimated', 'time', 'arrival', 'average'],
   component: () => import('./eta-calculator.vue'),
   icon: Hourglass,
diff --git a/src/tools/git-memo/index.ts b/src/tools/git-memo/index.ts
index c91ee813..f65ffe07 100644
--- a/src/tools/git-memo/index.ts
+++ b/src/tools/git-memo/index.ts
@@ -1,11 +1,11 @@
 import { BrandGit } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Git cheatsheet',
+  name: translate('tools.git-memo.title'),
   path: '/git-memo',
-  description:
-    'Git is a decentralized version management software. With this cheatsheet you will have a quick access to the most common git commands.',
+  description: translate('tools.git-memo.description'),
   keywords: ['git', 'push', 'force', 'pull', 'commit', 'amend', 'rebase', 'merge', 'reset', 'soft', 'hard', 'lease'],
   component: () => import('./git-memo.vue'),
   icon: BrandGit,
diff --git a/src/tools/hash-text/index.ts b/src/tools/hash-text/index.ts
index 3012747c..2070e41d 100644
--- a/src/tools/hash-text/index.ts
+++ b/src/tools/hash-text/index.ts
@@ -1,11 +1,11 @@
 import { EyeOff } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Hash text',
+  name: translate('tools.hash-text.title'),
   path: '/hash-text',
-  description:
-    'Hash a text string using the function you need : MD5, SHA1, SHA256, SHA224, SHA512, SHA384, SHA3 or RIPEMD160',
+  description: translate('tools.hash-text.description'),
   keywords: [
     'hash',
     'digest',
diff --git a/src/tools/hmac-generator/index.ts b/src/tools/hmac-generator/index.ts
index c0ca7da4..3500684e 100644
--- a/src/tools/hmac-generator/index.ts
+++ b/src/tools/hmac-generator/index.ts
@@ -1,11 +1,11 @@
 import { ShortTextRound } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Hmac generator',
+  name: translate('tools.hmac-generator.title'),
   path: '/hmac-generator',
-  description:
-    'Computes a hash-based message authentication code (HMAC) using a secret key and your favorite hashing function.',
+  description: translate('tools.hmac-generator.description'),
   keywords: ['hmac', 'generator', 'MD5', 'SHA1', 'SHA256', 'SHA224', 'SHA512', 'SHA384', 'SHA3', 'RIPEMD160'],
   component: () => import('./hmac-generator.vue'),
   icon: ShortTextRound,
diff --git a/src/tools/html-entities/index.ts b/src/tools/html-entities/index.ts
index 4907dc68..e292f087 100644
--- a/src/tools/html-entities/index.ts
+++ b/src/tools/html-entities/index.ts
@@ -1,10 +1,11 @@
 import { Code } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Escape html entities',
+  name: translate('tools.html-entities.title'),
   path: '/html-entities',
-  description: 'Escape or unescape html entities (replace <,>, &, " and \' to their html version)',
+  description: translate('tools.html-entities.description'),
   keywords: ['html', 'entities', 'escape', 'unescape', 'special', 'characters', 'tags'],
   component: () => import('./html-entities.vue'),
   icon: Code,
diff --git a/src/tools/html-wysiwyg-editor/editor/menu-bar.vue b/src/tools/html-wysiwyg-editor/editor/menu-bar.vue
index d3ad3168..9069673c 100644
--- a/src/tools/html-wysiwyg-editor/editor/menu-bar.vue
+++ b/src/tools/html-wysiwyg-editor/editor/menu-bar.vue
@@ -84,8 +84,8 @@ const items: MenuItem[] = [
     type: 'button',
     icon: H3,
     title: 'Heading 3',
-    action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(),
-    isActive: () => editor.value.isActive('heading', { level: 4 }),
+    action: () => editor.value.chain().focus().toggleHeading({ level: 3 }).run(),
+    isActive: () => editor.value.isActive('heading', { level: 3 }),
   },
   {
     type: 'button',
diff --git a/src/tools/html-wysiwyg-editor/index.ts b/src/tools/html-wysiwyg-editor/index.ts
index 461ad235..3a2ab007 100644
--- a/src/tools/html-wysiwyg-editor/index.ts
+++ b/src/tools/html-wysiwyg-editor/index.ts
@@ -1,10 +1,11 @@
 import { Edit } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'HTML WYSIWYG editor',
+  name: translate('tools.html-wysiwyg-editor.title'),
   path: '/html-wysiwyg-editor',
-  description: 'Online HTML editor with feature-rich WYSIWYG editor, get the source code of the content immediately.',
+  description: translate('tools.html-wysiwyg-editor.description'),
   keywords: ['html', 'wysiwyg', 'editor', 'p', 'ul', 'ol', 'converter', 'live'],
   component: () => import('./html-wysiwyg-editor.vue'),
   icon: Edit,
diff --git a/src/tools/http-status-codes/index.ts b/src/tools/http-status-codes/index.ts
index 43afae83..b3138943 100644
--- a/src/tools/http-status-codes/index.ts
+++ b/src/tools/http-status-codes/index.ts
@@ -2,11 +2,12 @@ import { HttpRound } from '@vicons/material';
 import { defineTool } from '../tool';
 
 import { codesByCategories } from './http-status-codes.constants';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'HTTP status codes',
+  name: translate('tools.http-status-codes.title'),
   path: '/http-status-codes',
-  description: 'The list of all HTTP status codes their name and their meaning.',
+  description: translate('tools.http-status-codes.description'),
   keywords: [
     'http',
     'status',
diff --git a/src/tools/iban-validator-and-parser/index.ts b/src/tools/iban-validator-and-parser/index.ts
index b0cae50d..ff7ff135 100644
--- a/src/tools/iban-validator-and-parser/index.ts
+++ b/src/tools/iban-validator-and-parser/index.ts
@@ -1,10 +1,11 @@
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 import Bank from '~icons/mdi/bank';
 
 export const tool = defineTool({
-  name: 'IBAN validator and parser',
+  name: translate('tools.iban-validator-and-parser.title'),
   path: '/iban-validator-and-parser',
-  description: 'Validate and parse IBAN numbers. Check if IBAN is valid and get the country, BBAN, if it is a QR-IBAN and the IBAN friendly format.',
+  description: translate('tools.iban-validator-and-parser.description'),
   keywords: ['iban', 'validator', 'and', 'parser', 'bic', 'bank'],
   component: () => import('./iban-validator-and-parser.vue'),
   icon: Bank,
diff --git a/src/tools/index.ts b/src/tools/index.ts
index 45c46131..e72b93d4 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -2,6 +2,17 @@ import { tool as base64FileConverter } from './base64-file-converter';
 import { tool as base64StringConverter } from './base64-string-converter';
 import { tool as basicAuthGenerator } from './basic-auth-generator';
 import { tool as dnsQueries } from './dns-queries';
+import { tool as emailNormalizer } from './email-normalizer';
+
+import { tool as asciiTextDrawer } from './ascii-text-drawer';
+
+import { tool as textToUnicode } from './text-to-unicode';
+import { tool as safelinkDecoder } from './safelink-decoder';
+import { tool as xmlToJson } from './xml-to-json';
+import { tool as jsonToXml } from './json-to-xml';
+import { tool as regexTester } from './regex-tester';
+import { tool as regexMemo } from './regex-memo';
+import { tool as markdownToHtml } from './markdown-to-html';
 import { tool as pdfSignatureChecker } from './pdf-signature-checker';
 import { tool as numeronymGenerator } from './numeronym-generator';
 import { tool as macAddressGenerator } from './mac-address-generator';
@@ -76,6 +87,7 @@ import { tool as urlParser } from './url-parser';
 import { tool as uuidGenerator } from './uuid-generator';
 import { tool as macAddressLookup } from './mac-address-lookup';
 import { tool as xmlFormatter } from './xml-formatter';
+import { tool as yamlViewer } from './yaml-viewer';
 
 export const toolsByCategory: ToolCategory[] = [
   {
@@ -94,6 +106,7 @@ export const toolsByCategory: ToolCategory[] = [
       caseConverter,
       textToNatoAlphabet,
       textToBinary,
+      textToUnicode,
       yamlToJson,
       yamlToToml,
       jsonToYaml,
@@ -101,6 +114,9 @@ export const toolsByCategory: ToolCategory[] = [
       listConverter,
       tomlToJson,
       tomlToYaml,
+      xmlToJson,
+      jsonToXml,
+      markdownToHtml,
     ],
   },
   {
@@ -121,6 +137,7 @@ export const toolsByCategory: ToolCategory[] = [
       userAgentParser,
       httpStatusCodes,
       jsonDiff,
+      safelinkDecoder,
     ],
   },
   {
@@ -140,6 +157,10 @@ export const toolsByCategory: ToolCategory[] = [
       chmodCalculator,
       dockerRunToDockerComposeConverter,
       xmlFormatter,
+      yamlViewer,
+      emailNormalizer,
+      regexTester,
+      regexMemo,
     ],
   },
   {
@@ -164,7 +185,15 @@ export const toolsByCategory: ToolCategory[] = [
   },
   {
     name: 'Text',
-    components: [loremIpsumGenerator, textStatistics, emojiPicker, stringObfuscator, textDiff, numeronymGenerator],
+    components: [
+      loremIpsumGenerator,
+      textStatistics,
+      emojiPicker,
+      stringObfuscator,
+      textDiff,
+      numeronymGenerator,
+      asciiTextDrawer,
+    ],
   },
   {
     name: 'Data',
diff --git a/src/tools/integer-base-converter/index.ts b/src/tools/integer-base-converter/index.ts
index 0008568c..f60d996d 100644
--- a/src/tools/integer-base-converter/index.ts
+++ b/src/tools/integer-base-converter/index.ts
@@ -1,10 +1,11 @@
 import { ArrowsLeftRight } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Integer base converter',
+  name: translate('tools.base-converter.title'),
   path: '/base-converter',
-  description: 'Convert number between different bases (decimal, hexadecimal, binary, octal, base64, ...)',
+  description: translate('tools.base-converter.description'),
   keywords: ['integer', 'number', 'base', 'conversion', 'decimal', 'hexadecimal', 'binary', 'octal', 'base64'],
   component: () => import('./integer-base-converter.vue'),
   icon: ArrowsLeftRight,
diff --git a/src/tools/integer-base-converter/integer-base-converter.model.test.ts b/src/tools/integer-base-converter/integer-base-converter.model.test.ts
index d0387b64..c7d7db79 100644
--- a/src/tools/integer-base-converter/integer-base-converter.model.test.ts
+++ b/src/tools/integer-base-converter/integer-base-converter.model.test.ts
@@ -11,6 +11,9 @@ describe('integer-base-converter', () => {
         expect(convertBase({ value: '10100101', fromBase: 2, toBase: 16 })).toEqual('a5');
         expect(convertBase({ value: '192654', fromBase: 10, toBase: 8 })).toEqual('570216');
         expect(convertBase({ value: 'zz', fromBase: 64, toBase: 10 })).toEqual('2275');
+        expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 10 })).toEqual('42540766411283223938465490632011909384');
+        expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 16 })).toEqual('20010db8000085a300000000ac1f8908');
+        expect(convertBase({ value: '20010db8000085a300000000ac1f8908', fromBase: 16, toBase: 10 })).toEqual('42540766411283223938465490632011909384');
       });
     });
   });
diff --git a/src/tools/integer-base-converter/integer-base-converter.model.ts b/src/tools/integer-base-converter/integer-base-converter.model.ts
index b4470e57..da0fe77f 100644
--- a/src/tools/integer-base-converter/integer-base-converter.model.ts
+++ b/src/tools/integer-base-converter/integer-base-converter.model.ts
@@ -5,16 +5,16 @@ export function convertBase({ value, fromBase, toBase }: { value: string; fromBa
   let decValue = value
     .split('')
     .reverse()
-    .reduce((carry: number, digit: string, index: number) => {
+    .reduce((carry: bigint, digit: string, index: number) => {
       if (!fromRange.includes(digit)) {
         throw new Error(`Invalid digit "${digit}" for base ${fromBase}.`);
       }
-      return (carry += fromRange.indexOf(digit) * fromBase ** index);
-    }, 0);
+      return (carry += BigInt(fromRange.indexOf(digit)) * BigInt(fromBase) ** BigInt(index));
+    }, 0n);
   let newValue = '';
   while (decValue > 0) {
-    newValue = toRange[decValue % toBase] + newValue;
-    decValue = (decValue - (decValue % toBase)) / toBase;
+    newValue = toRange[Number(decValue % BigInt(toBase))] + newValue;
+    decValue = (decValue - (decValue % BigInt(toBase))) / BigInt(toBase);
   }
   return newValue || '0';
 }
diff --git a/src/tools/ipv4-address-converter/index.ts b/src/tools/ipv4-address-converter/index.ts
index 62d2daf1..66ae03a3 100644
--- a/src/tools/ipv4-address-converter/index.ts
+++ b/src/tools/ipv4-address-converter/index.ts
@@ -1,10 +1,11 @@
 import { Binary } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Ipv4 address converter',
+  name: translate('tools.ipv4-address-converter.title'),
   path: '/ipv4-address-converter',
-  description: 'Convert an ip address into decimal, binary, hexadecimal or event in ipv6',
+  description: translate('tools.ipv4-address-converter.description'),
   keywords: ['ipv4', 'address', 'converter', 'decimal', 'hexadecimal', 'binary', 'ipv6'],
   component: () => import('./ipv4-address-converter.vue'),
   icon: Binary,
diff --git a/src/tools/ipv4-range-expander/index.ts b/src/tools/ipv4-range-expander/index.ts
index 233f7cc4..49dfae95 100644
--- a/src/tools/ipv4-range-expander/index.ts
+++ b/src/tools/ipv4-range-expander/index.ts
@@ -1,11 +1,11 @@
 import { UnfoldMoreOutlined } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'IPv4 range expander',
+  name: translate('tools.ipv4-range-expander.title'),
   path: '/ipv4-range-expander',
-  description:
-    'Given a start and an end IPv4 address this tool calculates a valid IPv4 network with its CIDR notation.',
+  description: translate('tools.ipv4-range-expander.description'),
   keywords: ['ipv4', 'range', 'expander', 'subnet', 'creator', 'cidr'],
   component: () => import('./ipv4-range-expander.vue'),
   icon: UnfoldMoreOutlined,
diff --git a/src/tools/ipv4-subnet-calculator/index.ts b/src/tools/ipv4-subnet-calculator/index.ts
index fb4bfb43..1bae7282 100644
--- a/src/tools/ipv4-subnet-calculator/index.ts
+++ b/src/tools/ipv4-subnet-calculator/index.ts
@@ -1,10 +1,11 @@
 import { RouterOutlined } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'IPv4 subnet calculator',
+  name: translate('tools.ipv4-subnet-calculator.title'),
   path: '/ipv4-subnet-calculator',
-  description: 'Parse your IPv4 CIDR blocks and get all the info you need about your sub network.',
+  description: translate('tools.ipv4-subnet-calculator.description'),
   keywords: ['ipv4', 'subnet', 'calculator', 'mask', 'network', 'cidr', 'netmask', 'bitmask', 'broadcast', 'address'],
   component: () => import('./ipv4-subnet-calculator.vue'),
   icon: RouterOutlined,
diff --git a/src/tools/ipv6-ula-generator/index.ts b/src/tools/ipv6-ula-generator/index.ts
index 24efaeba..51bfd6fc 100644
--- a/src/tools/ipv6-ula-generator/index.ts
+++ b/src/tools/ipv6-ula-generator/index.ts
@@ -1,10 +1,11 @@
 import { BuildingFactory } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'IPv6 ULA generator',
+  name: translate('tools.ipv6-ula-generator.title'),
   path: '/ipv6-ula-generator',
-  description: 'Generate your own local, non-routable IP addresses on your network according to RFC4193.',
+  description: translate('tools.ipv6-ula-generator.description'),
   keywords: ['ipv6', 'ula', 'generator', 'rfc4193', 'network', 'private'],
   component: () => import('./ipv6-ula-generator.vue'),
   icon: BuildingFactory,
diff --git a/src/tools/json-diff/index.ts b/src/tools/json-diff/index.ts
index 7c4c1eee..a4c0319c 100644
--- a/src/tools/json-diff/index.ts
+++ b/src/tools/json-diff/index.ts
@@ -1,10 +1,11 @@
 import { CompareArrowsRound } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'JSON diff',
+  name: translate('tools.json-diff.title'),
   path: '/json-diff',
-  description: 'Compare two JSON objects and get the differences between them.',
+  description: translate('tools.json-diff.description'),
   keywords: ['json', 'diff', 'compare', 'difference', 'object', 'data'],
   component: () => import('./json-diff.vue'),
   icon: CompareArrowsRound,
diff --git a/src/tools/json-minify/index.ts b/src/tools/json-minify/index.ts
index e6a02dbe..fbe5831b 100644
--- a/src/tools/json-minify/index.ts
+++ b/src/tools/json-minify/index.ts
@@ -1,10 +1,11 @@
 import { Braces } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'JSON minify',
+  name: translate('tools.json-minify.title'),
   path: '/json-minify',
-  description: 'Minify and compress your JSON by removing unnecessary white spaces.',
+  description: translate('tools.json-minify.description'),
   keywords: ['json', 'minify', 'format'],
   component: () => import('./json-minify.vue'),
   icon: Braces,
diff --git a/src/tools/json-to-csv/index.ts b/src/tools/json-to-csv/index.ts
index acfef02f..9f38b82f 100644
--- a/src/tools/json-to-csv/index.ts
+++ b/src/tools/json-to-csv/index.ts
@@ -1,10 +1,11 @@
 import { List } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'JSON to CSV',
+  name: translate('tools.json-to-csv.title'),
   path: '/json-to-csv',
-  description: 'Convert JSON to CSV with automatic header detection.',
+  description: translate('tools.json-to-csv.description'),
   keywords: ['json', 'to', 'csv', 'convert'],
   component: () => import('./json-to-csv.vue'),
   icon: List,
diff --git a/src/tools/json-to-toml/index.ts b/src/tools/json-to-toml/index.ts
index 13e45eaf..da42c18d 100644
--- a/src/tools/json-to-toml/index.ts
+++ b/src/tools/json-to-toml/index.ts
@@ -1,10 +1,11 @@
 import { Braces } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'JSON to TOML',
+  name: translate('tools.json-to-toml.title'),
   path: '/json-to-toml',
-  description: 'Parse and convert JSON to TOML.',
+  description: translate('tools.json-to-toml.description'),
   keywords: ['json', 'parse', 'toml', 'convert', 'transform'],
   component: () => import('./json-to-toml.vue'),
   icon: Braces,
diff --git a/src/tools/json-to-xml/index.ts b/src/tools/json-to-xml/index.ts
new file mode 100644
index 00000000..c35ace2b
--- /dev/null
+++ b/src/tools/json-to-xml/index.ts
@@ -0,0 +1,12 @@
+import { Braces } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+  name: 'JSON to XML',
+  path: '/json-to-xml',
+  description: 'Convert JSON to XML',
+  keywords: ['json', 'xml'],
+  component: () => import('./json-to-xml.vue'),
+  icon: Braces,
+  createdAt: new Date('2024-08-09'),
+});
diff --git a/src/tools/json-to-xml/json-to-xml.vue b/src/tools/json-to-xml/json-to-xml.vue
new file mode 100644
index 00000000..96a7cf16
--- /dev/null
+++ b/src/tools/json-to-xml/json-to-xml.vue
@@ -0,0 +1,32 @@
+
+
+
+  
+
diff --git a/src/tools/json-to-yaml-converter/index.ts b/src/tools/json-to-yaml-converter/index.ts
index 9db09d3e..c01e3ec0 100644
--- a/src/tools/json-to-yaml-converter/index.ts
+++ b/src/tools/json-to-yaml-converter/index.ts
@@ -1,10 +1,11 @@
 import { Braces } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'JSON to YAML converter',
+  name: translate('tools.json-to-yaml-converter.title'),
   path: '/json-to-yaml-converter',
-  description: 'Simply convert JSON to YAML with this live online converter.',
+  description: translate('tools.json-to-yaml-converter.description'),
   keywords: ['yaml', 'to', 'json'],
   component: () => import('./json-to-yaml.vue'),
   icon: Braces,
diff --git a/src/tools/json-viewer/index.ts b/src/tools/json-viewer/index.ts
index 6b5b8812..bc488245 100644
--- a/src/tools/json-viewer/index.ts
+++ b/src/tools/json-viewer/index.ts
@@ -1,10 +1,11 @@
 import { Braces } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'JSON prettify and format',
+  name: translate('tools.json-prettify.title'),
   path: '/json-prettify',
-  description: 'Prettify your JSON string to a human friendly readable format.',
+  description: translate('tools.json-prettify.description'),
   keywords: ['json', 'viewer', 'prettify', 'format'],
   component: () => import('./json-viewer.vue'),
   icon: Braces,
diff --git a/src/tools/jwt-parser/index.ts b/src/tools/jwt-parser/index.ts
index 7249ace0..939b4b34 100644
--- a/src/tools/jwt-parser/index.ts
+++ b/src/tools/jwt-parser/index.ts
@@ -1,10 +1,11 @@
 import { Key } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'JWT parser',
+  name: translate('tools.jwt-parser.title'),
   path: '/jwt-parser',
-  description: 'Parse and decode your JSON Web Token (jwt) and display its content.',
+  description: translate('tools.jwt-parser.description'),
   keywords: [
     'jwt',
     'parser',
diff --git a/src/tools/jwt-parser/jwt-parser.service.ts b/src/tools/jwt-parser/jwt-parser.service.ts
index cc39145a..543f4c8b 100644
--- a/src/tools/jwt-parser/jwt-parser.service.ts
+++ b/src/tools/jwt-parser/jwt-parser.service.ts
@@ -19,7 +19,7 @@ function decodeJwt({ jwt }: { jwt: string }) {
 
 function parseClaims({ claim, value }: { claim: string; value: unknown }) {
   const claimDescription = CLAIM_DESCRIPTIONS[claim];
-  const formattedValue = _.isPlainObject(value) ? JSON.stringify(value, null, 3) : _.toString(value);
+  const formattedValue = _.isPlainObject(value) || _.isArray(value) ? JSON.stringify(value, null, 3) : _.toString(value);
   const friendlyValue = getFriendlyValue({ claim, value });
 
   return {
diff --git a/src/tools/jwt-parser/jwt-parser.vue b/src/tools/jwt-parser/jwt-parser.vue
index 6b30fc0c..a26064d7 100644
--- a/src/tools/jwt-parser/jwt-parser.vue
+++ b/src/tools/jwt-parser/jwt-parser.vue
@@ -39,7 +39,7 @@ const validation = useValidation({
             {{ section.title }}
           
           
-            | + | {{ claim }}
               
@@ -47,7 +47,7 @@ const validation = useValidation({
                 ({{ claimDescription }})- | + | {{ value }}
               
                 ({{ friendlyValue }})
diff --git a/src/tools/keycode-info/index.ts b/src/tools/keycode-info/index.ts
index a9ffab2d..a2f36562 100644
--- a/src/tools/keycode-info/index.ts
+++ b/src/tools/keycode-info/index.ts
@@ -1,10 +1,11 @@
 import { Keyboard } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Keycode info',
+  name: translate('tools.keycode-info.title'),
   path: '/keycode-info',
-  description: 'Find the javascript keycode, code, location and modifiers of any pressed key.',
+  description: translate('tools.keycode-info.description'),
   keywords: [
     'keycode',
     'info',
diff --git a/src/tools/list-converter/index.ts b/src/tools/list-converter/index.ts
index cf9fbd39..9ae7c512 100644
--- a/src/tools/list-converter/index.ts
+++ b/src/tools/list-converter/index.ts
@@ -1,11 +1,11 @@
 import { List } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'List converter',
+  name: translate('tools.list-converter.title'),
   path: '/list-converter',
-  description:
-    'This tool can process column-based data and apply various changes (transpose, add prefix and suffix, reverse list, sort list, lowercase values, truncate values) to each row.',
+  description: translate('tools.list-converter.description'),
   keywords: ['list', 'converter', 'sort', 'reverse', 'prefix', 'suffix', 'lowercase', 'truncate'],
   component: () => import('./list-converter.vue'),
   icon: List,
diff --git a/src/tools/lorem-ipsum-generator/index.ts b/src/tools/lorem-ipsum-generator/index.ts
index 1767d85d..2634d9e0 100644
--- a/src/tools/lorem-ipsum-generator/index.ts
+++ b/src/tools/lorem-ipsum-generator/index.ts
@@ -1,11 +1,11 @@
 import { AlignJustified } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Lorem ipsum generator',
+  name: translate('tools.lorem-ipsum-generator.title'),
   path: '/lorem-ipsum-generator',
-  description:
-    'Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content',
+  description: translate('tools.lorem-ipsum-generator.description'),
   keywords: ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'placeholder', 'text', 'filler', 'random', 'generator'],
   component: () => import('./lorem-ipsum-generator.vue'),
   icon: AlignJustified,
diff --git a/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue b/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue
index 9085725f..ccd8b519 100644
--- a/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue
+++ b/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue
@@ -2,6 +2,7 @@
 import { generateLoremIpsum } from './lorem-ipsum-generator.service';
 import { useCopy } from '@/composable/copy';
 import { randIntFromInterval } from '@/utils/random';
+import { computedRefreshable } from '@/composable/computedRefreshable';
 
 const paragraphs = ref(1);
 const sentences = ref([3, 8]);
@@ -9,7 +10,7 @@ const words = ref([8, 15]);
 const startWithLoremIpsum = ref(true);
 const asHTML = ref(false);
 
-const loremIpsumText = computed(() =>
+const [loremIpsumText, refreshLoremIpsum] = computedRefreshable(() =>
   generateLoremIpsum({
     paragraphCount: paragraphs.value,
     asHTML: asHTML.value,
@@ -18,6 +19,7 @@ const loremIpsumText = computed(() =>
     startWithLoremIpsum: startWithLoremIpsum.value,
   }),
 );
+
 const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to the clipboard' });
 
 
@@ -41,10 +43,13 @@ const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to
 
     
 
- 
+     
       
         Copy
       
+      
+        Refresh
+      
      
   
 
diff --git a/src/tools/mac-address-generator/index.ts b/src/tools/mac-address-generator/index.ts
index 9d20fb69..3106bcec 100644
--- a/src/tools/mac-address-generator/index.ts
+++ b/src/tools/mac-address-generator/index.ts
@@ -1,10 +1,11 @@
 import { Devices } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'MAC address generator',
+  name: translate('tools.mac-address-generator.title'),
   path: '/mac-address-generator',
-  description: 'Enter the quantity and prefix. MAC addresses will be generated in your chosen case (uppercase or lowercase)',
+  description: translate('tools.mac-address-generator.description'),
   keywords: ['mac', 'address', 'generator', 'random', 'prefix'],
   component: () => import('./mac-address-generator.vue'),
   icon: Devices,
diff --git a/src/tools/mac-address-lookup/index.ts b/src/tools/mac-address-lookup/index.ts
index 4108bc33..367bcebb 100644
--- a/src/tools/mac-address-lookup/index.ts
+++ b/src/tools/mac-address-lookup/index.ts
@@ -1,10 +1,11 @@
 import { Devices } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'MAC address lookup',
+  name: translate('tools.mac-address-lookup.title'),
   path: '/mac-address-lookup',
-  description: 'Find the vendor and manufacturer of a device by its MAC address.',
+  description: translate('tools.mac-address-lookup.description'),
   keywords: ['mac', 'address', 'lookup', 'vendor', 'parser', 'manufacturer'],
   component: () => import('./mac-address-lookup.vue'),
   icon: Devices,
diff --git a/src/tools/markdown-to-html/index.ts b/src/tools/markdown-to-html/index.ts
new file mode 100644
index 00000000..73a6cfb3
--- /dev/null
+++ b/src/tools/markdown-to-html/index.ts
@@ -0,0 +1,12 @@
+import { Markdown } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+  name: 'Markdown to HTML',
+  path: '/markdown-to-html',
+  description: 'Convert Markdown to Html and allow to print (as PDF)',
+  keywords: ['markdown', 'html', 'converter', 'pdf'],
+  component: () => import('./markdown-to-html.vue'),
+  icon: Markdown,
+  createdAt: new Date('2024-08-25'),
+});
diff --git a/src/tools/markdown-to-html/markdown-to-html.vue b/src/tools/markdown-to-html/markdown-to-html.vue
new file mode 100644
index 00000000..c84d44ec
--- /dev/null
+++ b/src/tools/markdown-to-html/markdown-to-html.vue
@@ -0,0 +1,44 @@
+
+
++ +
+     
+
+     
+
+    +      
+ 
+
+     
+      
+        Print as PDF
+      
+     
+  
diff --git a/src/tools/math-evaluator/index.ts b/src/tools/math-evaluator/index.ts
index 8bf5ce2d..eb4290ba 100644
--- a/src/tools/math-evaluator/index.ts
+++ b/src/tools/math-evaluator/index.ts
@@ -1,10 +1,11 @@
 import { Math } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Math evaluator',
+  name: translate('tools.math-evaluator.title'),
   path: '/math-evaluator',
-  description: 'A calculator for evaluating mathematical expressions. You can use functions like sqrt, cos, sin, abs, etc.',
+  description: translate('tools.math-evaluator.description'),
   keywords: [
     'math',
     'evaluator',
diff --git a/src/tools/meta-tag-generator/index.ts b/src/tools/meta-tag-generator/index.ts
index c79b19f3..c6224410 100644
--- a/src/tools/meta-tag-generator/index.ts
+++ b/src/tools/meta-tag-generator/index.ts
@@ -1,10 +1,11 @@
 import { Tags } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Open graph meta generator',
+  name: translate('tools.og-meta-generator.title'),
   path: '/og-meta-generator',
-  description: 'Generate open-graph and socials html meta tags for your website.',
+  description: translate('tools.og-meta-generator.description'),
   keywords: [
     'meta',
     'tag',
diff --git a/src/tools/mime-types/index.ts b/src/tools/mime-types/index.ts
index 25e56404..da6ba0c4 100644
--- a/src/tools/mime-types/index.ts
+++ b/src/tools/mime-types/index.ts
@@ -1,10 +1,11 @@
 import { World } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Mime types',
+  name: translate('tools.mime-types.title'),
   path: '/mime-types',
-  description: 'Convert mime types to extensions and vice-versa.',
+  description: translate('tools.mime-types.description'),
   keywords: ['mime', 'types', 'extension', 'content', 'type'],
   component: () => import('./mime-types.vue'),
   icon: World,
diff --git a/src/tools/numeronym-generator/index.ts b/src/tools/numeronym-generator/index.ts
index 19686922..3d8472ae 100644
--- a/src/tools/numeronym-generator/index.ts
+++ b/src/tools/numeronym-generator/index.ts
@@ -1,10 +1,11 @@
 import { defineTool } from '../tool';
 import n7mIcon from './n7m-icon.svg?component';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Numeronym generator',
+  name: translate('tools.numeronym-generator.title'),
   path: '/numeronym-generator',
-  description: 'A numeronym is a word where a number is used to form an abbreviation. For example, "i18n" is a numeronym of "internationalization" where 18 stands for the number of letters between the first i and the last n in the word.',
+  description: translate('tools.numeronym-generator.description'),
   keywords: ['numeronym', 'generator', 'abbreviation', 'i18n', 'a11y', 'l10n'],
   component: () => import('./numeronym-generator.vue'),
   icon: n7mIcon,
diff --git a/src/tools/otp-code-generator-and-validator/index.ts b/src/tools/otp-code-generator-and-validator/index.ts
index 914368b6..42ecc9fc 100644
--- a/src/tools/otp-code-generator-and-validator/index.ts
+++ b/src/tools/otp-code-generator-and-validator/index.ts
@@ -1,10 +1,11 @@
 import { DeviceMobile } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'OTP code generator',
+  name: translate('tools.otp-generator.title'),
   path: '/otp-generator',
-  description: 'Generate and validate time-based OTP (one time password) for multi-factor authentication.',
+  description: translate('tools.otp-generator.description'),
   keywords: [
     'otp',
     'code',
diff --git a/src/tools/password-strength-analyser/index.ts b/src/tools/password-strength-analyser/index.ts
index 7b86bdd5..f2e89ef9 100644
--- a/src/tools/password-strength-analyser/index.ts
+++ b/src/tools/password-strength-analyser/index.ts
@@ -1,10 +1,11 @@
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 import PasswordIcon from '~icons/mdi/form-textbox-password';
 
 export const tool = defineTool({
-  name: 'Password strength analyser',
+  name: translate('tools.password-strength-analyser.title'),
   path: '/password-strength-analyser',
-  description: 'Discover the strength of your password with this client side only password strength analyser and crack time estimation tool.',
+  description: translate('tools.password-strength-analyser.description'),
   keywords: ['password', 'strength', 'analyser', 'and', 'crack', 'time', 'estimation', 'brute', 'force', 'attack', 'entropy', 'cracking', 'hash', 'hashing', 'algorithm', 'algorithms', 'md5', 'sha1', 'sha256', 'sha512', 'bcrypt', 'scrypt', 'argon2', 'argon2id', 'argon2i', 'argon2d'],
   component: () => import('./password-strength-analyser.vue'),
   icon: PasswordIcon,
diff --git a/src/tools/pdf-signature-checker/index.ts b/src/tools/pdf-signature-checker/index.ts
index 54563979..8b5d356b 100644
--- a/src/tools/pdf-signature-checker/index.ts
+++ b/src/tools/pdf-signature-checker/index.ts
@@ -1,10 +1,11 @@
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 import FileCertIcon from '~icons/mdi/file-certificate-outline';
 
 export const tool = defineTool({
-  name: 'PDF signature checker',
+  name: translate('tools.pdf-signature-checker.title'),
   path: '/pdf-signature-checker',
-  description: 'Verify the signatures of a PDF file. A signed PDF file contains one or more signatures that may be used to determine whether the contents of the file have been altered since the file was signed.',
+  description: translate('tools.pdf-signature-checker.description'),
   keywords: ['pdf', 'signature', 'checker', 'verify', 'validate', 'sign'],
   component: () => import('./pdf-signature-checker.vue'),
   icon: FileCertIcon,
diff --git a/src/tools/percentage-calculator/index.ts b/src/tools/percentage-calculator/index.ts
index 736f5706..33c5b2f1 100644
--- a/src/tools/percentage-calculator/index.ts
+++ b/src/tools/percentage-calculator/index.ts
@@ -1,10 +1,11 @@
 import { Percentage } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Percentage calculator',
+  name: translate('tools.percentage-calculator.title'),
   path: '/percentage-calculator',
-  description: 'Easily calculate percentages from a value to another value, or from a percentage to a value.',
+  description: translate('tools.percentage-calculator.description'),
   keywords: ['percentage', 'calculator', 'calculate', 'value', 'number', '%'],
   component: () => import('./percentage-calculator.vue'),
   icon: Percentage,
diff --git a/src/tools/phone-parser-and-formatter/index.ts b/src/tools/phone-parser-and-formatter/index.ts
index 5b19ae61..094b21e8 100644
--- a/src/tools/phone-parser-and-formatter/index.ts
+++ b/src/tools/phone-parser-and-formatter/index.ts
@@ -1,11 +1,11 @@
 import { Phone } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Phone parser and formatter',
+  name: translate('tools.phone-parser-and-formatter.title'),
   path: '/phone-parser-and-formatter',
-  description:
-    'Parse, validate and format phone numbers. Get information about the phone number, like the country code, type, etc.',
+  description: translate('tools.phone-parser-and-formatter.description'),
   keywords: [
     'phone',
     'parser',
diff --git a/src/tools/qr-code-generator/index.ts b/src/tools/qr-code-generator/index.ts
index 4c2f86bb..b97b4cbc 100644
--- a/src/tools/qr-code-generator/index.ts
+++ b/src/tools/qr-code-generator/index.ts
@@ -1,11 +1,11 @@
 import { Qrcode } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'QR Code generator',
+  name: translate('tools.qrcode-generator.title'),
   path: '/qrcode-generator',
-  description:
-    'Generate and download QR-code for an url or just a text and customize the background and foreground colors.',
+  description: translate('tools.qrcode-generator.description'),
   keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent'],
   component: () => import('./qr-code-generator.vue'),
   icon: Qrcode,
diff --git a/src/tools/random-port-generator/index.ts b/src/tools/random-port-generator/index.ts
index febdc2a4..e300b8f0 100644
--- a/src/tools/random-port-generator/index.ts
+++ b/src/tools/random-port-generator/index.ts
@@ -1,10 +1,11 @@
 import { Server } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Random port generator',
+  name: translate('tools.random-port-generator.title'),
   path: '/random-port-generator',
-  description: 'Generate random port numbers outside of the range of "known" ports (0-1023).',
+  description: translate('tools.random-port-generator.description'),
   keywords: ['system', 'port', 'lan', 'generator', 'random', 'development', 'computer'],
   component: () => import('./random-port-generator.vue'),
   icon: Server,
diff --git a/src/tools/regex-memo/index.ts b/src/tools/regex-memo/index.ts
new file mode 100644
index 00000000..f1f56489
--- /dev/null
+++ b/src/tools/regex-memo/index.ts
@@ -0,0 +1,12 @@
+import { BrandJavascript } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+  name: 'Regex cheatsheet',
+  path: '/regex-memo',
+  description: 'Javascript Regex/Regular Expression cheatsheet',
+  keywords: ['regex', 'regular', 'expression', 'javascript', 'memo', 'cheatsheet'],
+  component: () => import('./regex-memo.vue'),
+  icon: BrandJavascript,
+  createdAt: new Date('2024-09-20'),
+});
diff --git a/src/tools/regex-memo/regex-memo.content.md b/src/tools/regex-memo/regex-memo.content.md
new file mode 100644
index 00000000..0f779401
--- /dev/null
+++ b/src/tools/regex-memo/regex-memo.content.md
@@ -0,0 +1,121 @@
+### Normal characters
+
+Expression | Description
+:--|:--
+`.` or `[^\n\r]` | any character *excluding* a newline or carriage return
+`[A-Za-z]` | alphabet
+`[a-z]` | lowercase alphabet
+`[A-Z]` | uppercase alphabet
+`\d` or `[0-9]` | digit
+`\D` or `[^0-9]` | non-digit
+`_` | underscore
+`\w` or `[A-Za-z0-9_]` | alphabet, digit or underscore
+`\W` or `[^A-Za-z0-9_]` | inverse of `\w`
+`\S` | inverse of `\s`
+
+### Whitespace characters
+
+Expression | Description
+:--|:--
+` ` | space
+`\t` | tab
+`\n` | newline
+`\r` | carriage return
+`\s` | space, tab, newline or carriage return
+
+### Character set
+
+Expression | Description
+:--|:--
+`[xyz]` | either `x`, `y` or `z`
+`[^xyz]` | neither `x`, `y` nor `z`
+`[1-3]` | either `1`, `2` or `3`
+`[^1-3]` | neither `1`, `2` nor `3`
+
+- Think of a character set as an `OR` operation on the single characters that are enclosed between the square brackets.
+- Use `^` after the opening `[` to “negate” the character set.
+- Within a character set, `.` means a literal period.
+
+### Characters that require escaping
+
+#### Outside a character set
+
+Expression | Description
+:--|:--
+`\.` | period
+`\^` | caret
+`\$` | dollar sign
+`\|` | pipe
+`\\` | back slash
+`\/` | forward slash
+`\(` | opening bracket
+`\)` | closing bracket
+`\[` | opening square bracket
+`\]` | closing square bracket
+`\{` | opening curly bracket
+`\}` | closing curly bracket
+
+#### Inside a character set
+
+Expression | Description
+:--|:--
+`\\` | back slash
+`\]` | closing square bracket
+
+- A `^` must be escaped only if it occurs immediately after the opening `[` of the character set.
+- A `-` must be escaped only if it occurs between two alphabets or two digits.
+
+### Quantifiers
+
+Expression | Description
+:--|:--
+`{2}` | exactly 2
+`{2,}` | at least 2
+`{2,7}` | at least 2 but no more than 7
+`*` | 0 or more
+`+` | 1 or more
+`?` | exactly 0 or 1
+
+- The quantifier goes *after* the expression to be quantified.
+
+### Boundaries
+
+Expression | Description
+:--|:--
+`^` | start of string
+`$` | end of string
+`\b` | word boundary
+
+- How word boundary matching works:
+    - At the beginning of the string if the first character is `\w`.
+    - Between two adjacent characters within the string, if the first character is `\w` and the second character is `\W`.
+    - At the end of the string if the last character is `\w`.
+
+### Matching
+
+Expression | Description
+:--|:--
+`foo\|bar` | match either `foo` or `bar`
+`foo(?=bar)` | match `foo` if it’s before `bar`
+`foo(?!bar)` | match `foo` if it’s *not* before `bar`
+`(?<=bar)foo` | match `foo` if it’s after `bar`
+`(?
+import { useThemeVars } from 'naive-ui';
+import Memo from './regex-memo.content.md';
+
+const themeVars = useThemeVars();
+
+
++ 
+    
+  + 
+
+
diff --git a/src/tools/regex-tester/index.ts b/src/tools/regex-tester/index.ts
new file mode 100644
index 00000000..62a5e234
--- /dev/null
+++ b/src/tools/regex-tester/index.ts
@@ -0,0 +1,12 @@
+import { Language } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+  name: 'Regex Tester',
+  path: '/regex-tester',
+  description: 'Test your regular expressions with sample text.',
+  keywords: ['regex', 'tester', 'sample', 'expression'],
+  component: () => import('./regex-tester.vue'),
+  icon: Language,
+  createdAt: new Date('2024-09-20'),
+});
diff --git a/src/tools/regex-tester/regex-tester.service.test.ts b/src/tools/regex-tester/regex-tester.service.test.ts
new file mode 100644
index 00000000..bd4efbbc
--- /dev/null
+++ b/src/tools/regex-tester/regex-tester.service.test.ts
@@ -0,0 +1,106 @@
+import { describe, expect, it } from 'vitest';
+import { matchRegex } from './regex-tester.service';
+
+const regexesData = [
+  {
+    regex: '',
+    text: '',
+    flags: '',
+    result: [],
+  },
+  {
+    regex: '.*',
+    text: '',
+    flags: '',
+    result: [],
+  },
+  {
+    regex: '',
+    text: 'aaa',
+    flags: '',
+    result: [],
+  },
+  {
+    regex: 'a',
+    text: 'baaa',
+    flags: '',
+    result: [
+      {
+        captures: [],
+        groups: [],
+        index: 1,
+        value: 'a',
+      },
+    ],
+  },
+  {
+    regex: '(.)(?r)',
+    text: 'azertyr',
+    flags: 'g',
+    result: [
+      {
+        captures: [
+          {
+            end: 3,
+            name: '1',
+            start: 2,
+            value: 'e',
+          },
+          {
+            end: 4,
+            name: '2',
+            start: 3,
+            value: 'r',
+          },
+        ],
+        groups: [
+          {
+            end: 4,
+            name: 'g',
+            start: 3,
+            value: 'r',
+          },
+        ],
+        index: 2,
+        value: 'er',
+      },
+      {
+        captures: [
+          {
+            end: 6,
+            name: '1',
+            start: 5,
+            value: 'y',
+          },
+          {
+            end: 7,
+            name: '2',
+            start: 6,
+            value: 'r',
+          },
+        ],
+        groups: [
+          {
+            end: 7,
+            name: 'g',
+            start: 6,
+            value: 'r',
+          },
+        ],
+        index: 5,
+        value: 'yr',
+      },
+    ],
+  },
+];
+
+describe('regex-tester', () => {
+  for (const reg of regexesData) {
+    const { regex, text, flags, result: expected_result } = reg;
+    it(`Should matchRegex("${regex}","${text}","${flags}") return correct result`, async () => {
+      const result = matchRegex(regex, text, `${flags}d`);
+
+      expect(result).to.deep.equal(expected_result);
+    });
+  }
+});
diff --git a/src/tools/regex-tester/regex-tester.service.ts b/src/tools/regex-tester/regex-tester.service.ts
new file mode 100644
index 00000000..ec8682c5
--- /dev/null
+++ b/src/tools/regex-tester/regex-tester.service.ts
@@ -0,0 +1,61 @@
+interface RegExpGroupIndices {
+  [name: string]: [number, number]
+}
+interface RegExpIndices extends Array<[number, number]> {
+  groups: RegExpGroupIndices
+}
+interface RegExpExecArrayWithIndices extends RegExpExecArray {
+  indices: RegExpIndices
+}
+interface GroupCapture {
+  name: string
+  value: string
+  start: number
+  end: number
+};
+
+export function matchRegex(regex: string, text: string, flags: string) {
+  // if (regex === '' || text === '') {
+  //   return [];
+  // }
+
+  let lastIndex = -1;
+  const re = new RegExp(regex, flags);
+  const results = [];
+  let match = re.exec(text) as RegExpExecArrayWithIndices;
+  while (match !== null) {
+    if (re.lastIndex === lastIndex || match[0] === '') {
+      break;
+    }
+    const indices = match.indices;
+    const captures: Array = [];
+    Object.entries(match).forEach(([captureName, captureValue]) => {
+      if (captureName !== '0' && captureName.match(/\d+/)) {
+        captures.push({
+          name: captureName,
+          value: captureValue,
+          start: indices[Number(captureName)][0],
+          end: indices[Number(captureName)][1],
+        });
+      }
+    });
+    const groups: Array = [];
+    Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => {
+      groups.push({
+        name: groupName,
+        value: groupValue,
+        start: indices.groups[groupName][0],
+        end: indices.groups[groupName][1],
+      });
+    });
+    results.push({
+      index: match.index,
+      value: match[0],
+      captures,
+      groups,
+    });
+    lastIndex = re.lastIndex;
+    match = re.exec(text) as RegExpExecArrayWithIndices;
+  }
+  return results;
+}
diff --git a/src/tools/regex-tester/regex-tester.vue b/src/tools/regex-tester/regex-tester.vue
new file mode 100644
index 00000000..a1fa7958
--- /dev/null
+++ b/src/tools/regex-tester/regex-tester.vue
@@ -0,0 +1,193 @@
+
+
+
+ +
diff --git a/src/tools/roman-numeral-converter/index.ts b/src/tools/roman-numeral-converter/index.ts
index f2dbdc0a..6929747f 100644
--- a/src/tools/roman-numeral-converter/index.ts
+++ b/src/tools/roman-numeral-converter/index.ts
@@ -1,10 +1,11 @@
 import { LetterX } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Roman numeral converter',
+  name: translate('tools.roman-numeral-converter.title'),
   path: '/roman-numeral-converter',
-  description: 'Convert Roman numerals to numbers and convert numbers to Roman numerals.',
+  description: translate('tools.roman-numeral-converter.description'),
   keywords: ['roman', 'arabic', 'converter', 'X', 'I', 'V', 'L', 'C', 'D', 'M'],
   component: () => import('./roman-numeral-converter.vue'),
   icon: LetterX,
diff --git a/src/tools/rsa-key-pair-generator/index.ts b/src/tools/rsa-key-pair-generator/index.ts
index c8ab4cdb..3d034e5b 100644
--- a/src/tools/rsa-key-pair-generator/index.ts
+++ b/src/tools/rsa-key-pair-generator/index.ts
@@ -1,10 +1,11 @@
 import { Certificate } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'RSA key pair generator',
+  name: translate('tools.rsa-key-pair-generator.title'),
   path: '/rsa-key-pair-generator',
-  description: 'Generate new random RSA private and public key pem certificates.',
+  description: translate('tools.rsa-key-pair-generator.description'),
   keywords: ['rsa', 'key', 'pair', 'generator', 'public', 'private', 'secret', 'ssh', 'pem'],
   component: () => import('./rsa-key-pair-generator.vue'),
   icon: Certificate,
diff --git a/src/tools/safelink-decoder/index.ts b/src/tools/safelink-decoder/index.ts
new file mode 100644
index 00000000..ef865108
--- /dev/null
+++ b/src/tools/safelink-decoder/index.ts
@@ -0,0 +1,12 @@
+import { Mailbox } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+  name: 'Outlook Safelink decoder',
+  path: '/safelink-decoder',
+  description: 'Decode Outlook SafeLink links',
+  keywords: ['outlook', 'safelink', 'decoder'],
+  component: () => import('./safelink-decoder.vue'),
+  icon: Mailbox,
+  createdAt: new Date('2024-03-11'),
+});
diff --git a/src/tools/safelink-decoder/safelink-decoder.service.test.ts b/src/tools/safelink-decoder/safelink-decoder.service.test.ts
new file mode 100644
index 00000000..b601f01e
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.service.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from 'vitest';
+import { decodeSafeLinksURL } from './safelink-decoder.service';
+
+describe('safelink-decoder', () => {
+  describe('decodeSafeLinksURL', () => {
+    describe('decode outlook safelink urls', () => {
+      it('should decode basic safelink urls', () => {
+        expect(decodeSafeLinksURL('https://aus01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3Dsafelink%26rlz%3D1&data=05%7C02%7C%7C1ed07253975b46da1d1508dc3443752a%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638442711583216725%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=%2BQY0HBnnxfI7pzZoxzlhZdDvYu80LwQB0zUUjrffVnk%3D&reserved=0'))
+          .toBe('https://www.google.com/search?q=safelink&rlz=1');
+      });
+      it('should decode encoded safelink urls', () => {
+        expect(decodeSafeLinksURL('https://aus01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3Dsafelink%26rlz%3D1&data=05%7C02%7C%7C1ed07253975b46da1d1508dc3443752a%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638442711583216725%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=%2BQY0HBnnxfI7pzZoxzlhZdDvYu80LwQB0zUUjrffVnk%3D&reserved=0'))
+          .toBe('https://www.google.com/search?q=safelink&rlz=1');
+      });
+      it('throw on not outlook safelink urls', () => {
+        expect(() => decodeSafeLinksURL('https://google.com'))
+          .toThrow('Invalid SafeLinks URL provided');
+      });
+    });
+  });
+});
diff --git a/src/tools/safelink-decoder/safelink-decoder.service.ts b/src/tools/safelink-decoder/safelink-decoder.service.ts
new file mode 100644
index 00000000..96be00ab
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.service.ts
@@ -0,0 +1,7 @@
+export function decodeSafeLinksURL(safeLinksUrl: string) {
+  if (!safeLinksUrl.match(/\.safelinks\.protection\.outlook\.com/)) {
+    throw new Error('Invalid SafeLinks URL provided');
+  }
+
+  return new URL(safeLinksUrl).searchParams.get('url');
+}
diff --git a/src/tools/safelink-decoder/safelink-decoder.vue b/src/tools/safelink-decoder/safelink-decoder.vue
new file mode 100644
index 00000000..01337eb2
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.vue
@@ -0,0 +1,32 @@
+
+
+
+
+    +      
+      
+        See Regular Expression Cheatsheet
+      
+      
+        
+          Global search. ( g)
+        
+        
+          Case-insensitive search. (i)
+        
+        
+          Multiline(m)
+        
+        
+          Singleline(s)
+        
+        
+          Unicode(u)
+        
+        
+          Unicode Sets (v)
+        
+      
+
+      
+
+      
+
+
+    +      
+        
+ 
+            +        
+        
+| +              Index in text
++ | +              Value
++ | +              Captures
++ | +              Groups
++ |  
+            +        
+      
+      
+        No match
+      
+| {{ match.index }}+ | {{ match.value }}+ | + +
+                +
+                  "{{ capture.name }}" = {{ capture.value }} [{{ capture.start }} - {{ capture.end }}]
+                + | + +
+                +
+                  "{{ group.name }}" = {{ group.value }} [{{ group.start }} - {{ group.end }}]
+                + |  
+
+    + {{ sample }}+
+
+    +      
+ 
+      
+ 
+  
+    
+
+    
+
+    
+      
+    
+  +
diff --git a/src/tools/slugify-string/index.ts b/src/tools/slugify-string/index.ts
index 8dabcdb1..1f1bfcf3 100644
--- a/src/tools/slugify-string/index.ts
+++ b/src/tools/slugify-string/index.ts
@@ -1,10 +1,11 @@
 import { AbcRound } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Slugify string',
+  name: translate('tools.slugify-string.title'),
   path: '/slugify-string',
-  description: 'Make a string url, filename and id safe.',
+  description: translate('tools.slugify-string.description'),
   keywords: ['slugify', 'string', 'escape', 'emoji', 'special', 'character', 'space', 'trim'],
   component: () => import('./slugify-string.vue'),
   icon: AbcRound,
diff --git a/src/tools/sql-prettify/index.ts b/src/tools/sql-prettify/index.ts
index 426845fb..96bff0fe 100644
--- a/src/tools/sql-prettify/index.ts
+++ b/src/tools/sql-prettify/index.ts
@@ -1,10 +1,11 @@
 import { Database } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'SQL prettify and format',
+  name: translate('tools.sql-prettify.title'),
   path: '/sql-prettify',
-  description: 'Format and prettify your SQL queries online (it supports various SQL dialects).',
+  description: translate('tools.sql-prettify.description'),
   keywords: [
     'sql',
     'prettify',
diff --git a/src/tools/string-obfuscator/index.ts b/src/tools/string-obfuscator/index.ts
index d5b45318..67f1995c 100644
--- a/src/tools/string-obfuscator/index.ts
+++ b/src/tools/string-obfuscator/index.ts
@@ -1,10 +1,11 @@
 import { EyeOff } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'String obfuscator',
+  name: translate('tools.string-obfuscator.title'),
   path: '/string-obfuscator',
-  description: 'Obfuscate a string (like a secret, an IBAN, or a token) to make it shareable and identifiable without revealing its content.',
+  description: translate('tools.string-obfuscator.description'),
   keywords: ['string', 'obfuscator', 'secret', 'token', 'hide', 'obscure', 'mask', 'masking'],
   component: () => import('./string-obfuscator.vue'),
   icon: EyeOff,
diff --git a/src/tools/svg-placeholder-generator/index.ts b/src/tools/svg-placeholder-generator/index.ts
index d676294c..37a709eb 100644
--- a/src/tools/svg-placeholder-generator/index.ts
+++ b/src/tools/svg-placeholder-generator/index.ts
@@ -1,10 +1,11 @@
 import { ImageOutlined } from '@vicons/material';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'SVG placeholder generator',
+  name: translate('tools.svg-placeholder-generator.title'),
   path: '/svg-placeholder-generator',
-  description: 'Generate svg images to use as placeholder in your applications.',
+  description: translate('tools.svg-placeholder-generator.description'),
   keywords: ['svg', 'placeholder', 'generator', 'image', 'size', 'mockup'],
   component: () => import('./svg-placeholder-generator.vue'),
   icon: ImageOutlined,
diff --git a/src/tools/temperature-converter/index.ts b/src/tools/temperature-converter/index.ts
index 60192b41..3f526ee2 100644
--- a/src/tools/temperature-converter/index.ts
+++ b/src/tools/temperature-converter/index.ts
@@ -1,11 +1,11 @@
 import { Temperature } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Temperature converter',
+  name: translate('tools.temperature-converter.title'),
   path: '/temperature-converter',
-  description:
-    'Temperature degrees conversions for Kelvin, Celsius, Fahrenheit, Rankine, Delisle, Newton, Réaumur and Rømer.',
+  description: translate('tools.temperature-converter.description'),
   keywords: [
     'temperature',
     'converter',
diff --git a/src/tools/text-diff/index.ts b/src/tools/text-diff/index.ts
index 992acbae..de124ee6 100644
--- a/src/tools/text-diff/index.ts
+++ b/src/tools/text-diff/index.ts
@@ -1,10 +1,11 @@
 import { FileDiff } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Text diff',
+  name: translate('tools.text-diff.title'),
   path: '/text-diff',
-  description: 'Compare two texts and see the differences between them.',
+  description: translate('tools.text-diff.description'),
   keywords: ['text', 'diff', 'compare', 'string', 'text diff', 'code'],
   component: () => import('./text-diff.vue'),
   icon: FileDiff,
diff --git a/src/tools/text-statistics/index.ts b/src/tools/text-statistics/index.ts
index 0e54b71b..23937839 100644
--- a/src/tools/text-statistics/index.ts
+++ b/src/tools/text-statistics/index.ts
@@ -1,10 +1,11 @@
 import { FileText } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Text statistics',
+  name: translate('tools.text-statistics.title'),
   path: '/text-statistics',
-  description: 'Get information about a text, the amount of characters, the amount of words, it\'s size, ...',
+  description: translate('tools.text-statistics.description'),
   keywords: ['text', 'statistics', 'length', 'characters', 'count', 'size', 'bytes'],
   component: () => import('./text-statistics.vue'),
   icon: FileText,
diff --git a/src/tools/text-to-binary/index.ts b/src/tools/text-to-binary/index.ts
index 40ac93d6..ce0f87ea 100644
--- a/src/tools/text-to-binary/index.ts
+++ b/src/tools/text-to-binary/index.ts
@@ -1,10 +1,11 @@
 import { Binary } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Text to ASCII binary',
+  name: translate('tools.text-to-binary.title'),
   path: '/text-to-binary',
-  description: 'Convert text to its ASCII binary representation and vice versa.',
+  description: translate('tools.text-to-binary.description'),
   keywords: ['text', 'to', 'binary', 'converter', 'encode', 'decode', 'ascii'],
   component: () => import('./text-to-binary.vue'),
   icon: Binary,
diff --git a/src/tools/text-to-nato-alphabet/index.ts b/src/tools/text-to-nato-alphabet/index.ts
index 100476a0..43b72fb4 100644
--- a/src/tools/text-to-nato-alphabet/index.ts
+++ b/src/tools/text-to-nato-alphabet/index.ts
@@ -1,10 +1,11 @@
 import { Speakerphone } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Text to NATO alphabet',
+  name: translate('tools.text-to-nato-alphabet.title'),
   path: '/text-to-nato-alphabet',
-  description: 'Transform text into NATO phonetic alphabet for oral transmission.',
+  description: translate('tools.text-to-nato-alphabet.description'),
   keywords: ['string', 'nato', 'alphabet', 'phonetic', 'oral', 'transmission'],
   component: () => import('./text-to-nato-alphabet.vue'),
   icon: Speakerphone,
diff --git a/src/tools/text-to-unicode/index.ts b/src/tools/text-to-unicode/index.ts
new file mode 100644
index 00000000..80396026
--- /dev/null
+++ b/src/tools/text-to-unicode/index.ts
@@ -0,0 +1,13 @@
+import { TextWrap } from '@vicons/tabler';
+import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
+
+export const tool = defineTool({
+  name: translate('tools.text-to-unicode.title'),
+  path: '/text-to-unicode',
+  description: translate('tools.text-to-unicode.description'),
+  keywords: ['text', 'to', 'unicode'],
+  component: () => import('./text-to-unicode.vue'),
+  icon: TextWrap,
+  createdAt: new Date('2024-01-31'),
+});
diff --git a/src/tools/text-to-unicode/text-to-unicode.e2e.spec.ts b/src/tools/text-to-unicode/text-to-unicode.e2e.spec.ts
new file mode 100644
index 00000000..761828fd
--- /dev/null
+++ b/src/tools/text-to-unicode/text-to-unicode.e2e.spec.ts
@@ -0,0 +1,25 @@
+import { expect, test } from '@playwright/test';
+
+test.describe('Tool - Text to Unicode', () => {
+  test.beforeEach(async ({ page }) => {
+    await page.goto('/text-to-unicode');
+  });
+
+  test('Has correct title', async ({ page }) => {
+    await expect(page).toHaveTitle('Text to Unicode - IT Tools');
+  });
+
+  test('Text to unicode conversion', async ({ page }) => {
+    await page.getByTestId('text-to-unicode-input').fill('it-tools');
+    const unicode = await page.getByTestId('text-to-unicode-output').inputValue();
+
+    expect(unicode).toEqual('it-tools');
+  });
+
+  test('Unicode to text conversion', async ({ page }) => {
+    await page.getByTestId('unicode-to-text-input').fill('it-tools');
+    const text = await page.getByTestId('unicode-to-text-output').inputValue();
+
+    expect(text).toEqual('it-tools');
+  });
+});
diff --git a/src/tools/text-to-unicode/text-to-unicode.service.test.ts b/src/tools/text-to-unicode/text-to-unicode.service.test.ts
new file mode 100644
index 00000000..bda4fa7a
--- /dev/null
+++ b/src/tools/text-to-unicode/text-to-unicode.service.test.ts
@@ -0,0 +1,20 @@
+import { describe, expect, it } from 'vitest';
+import { convertTextToUnicode, convertUnicodeToText } from './text-to-unicode.service';
+
+describe('text-to-unicode', () => {
+  describe('convertTextToUnicode', () => {
+    it('a text string is converted to unicode representation', () => {
+      expect(convertTextToUnicode('A')).toBe('A');
+      expect(convertTextToUnicode('linke the string convert to unicode')).toBe('linke the string convert to unicode');
+      expect(convertTextToUnicode('')).toBe('');
+    });
+  });
+
+  describe('convertUnicodeToText', () => {
+    it('an unicode string is converted to its text representation', () => {
+      expect(convertUnicodeToText('A')).toBe('A');
+      expect(convertUnicodeToText('linke the string convert to unicode')).toBe('linke the string convert to unicode');
+      expect(convertUnicodeToText('')).toBe('');
+    });
+  });
+});
diff --git a/src/tools/text-to-unicode/text-to-unicode.service.ts b/src/tools/text-to-unicode/text-to-unicode.service.ts
new file mode 100644
index 00000000..e7772cf8
--- /dev/null
+++ b/src/tools/text-to-unicode/text-to-unicode.service.ts
@@ -0,0 +1,9 @@
+function convertTextToUnicode(text: string): string {
+  return text.split('').map(value => `${value.charCodeAt(0)};`).join('');
+}
+
+function convertUnicodeToText(unicodeStr: string): string {
+  return unicodeStr.replace(/(\d+);/g, (match, dec) => String.fromCharCode(dec));
+}
+
+export { convertTextToUnicode, convertUnicodeToText };
diff --git a/src/tools/text-to-unicode/text-to-unicode.vue b/src/tools/text-to-unicode/text-to-unicode.vue
new file mode 100644
index 00000000..be9bed86
--- /dev/null
+++ b/src/tools/text-to-unicode/text-to-unicode.vue
@@ -0,0 +1,34 @@
+
+
+
+  
+    
+    
+ 
+      
+        Copy unicode to clipboard
+      
+    +  
+
+  
+    
+    
+ 
+      
+        Copy text to clipboard
+      
+    +  
+
diff --git a/src/tools/token-generator/locales/en.yml b/src/tools/token-generator/locales/en.yml
deleted file mode 100644
index 7a06f3dd..00000000
--- a/src/tools/token-generator/locales/en.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-tools:
-  token-generator:
-    title: Token generator
-    description: Generate random string with the chars you want, uppercase or lowercase letters, numbers and/or symbols.
-
-    uppercase: Uppercase (ABC...)
-    lowercase: Lowercase (abc...)
-    numbers: Numbers (123...)
-    symbols: Symbols (!-;...)
-    length: Length
-    tokenPlaceholder: 'The token...'
-    copied: Token copied to the clipboard
-    button:
-      copy: Copy
-      refresh: Refresh
\ No newline at end of file
diff --git a/src/tools/token-generator/locales/fr.yml b/src/tools/token-generator/locales/fr.yml
deleted file mode 100644
index e9605567..00000000
--- a/src/tools/token-generator/locales/fr.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-tools:
-  token-generator:
-    title: Générateur de token
-    description: >-
-      Génère une chaîne aléatoire avec les caractères que vous voulez, lettres
-      majuscules ou minuscules, chiffres et/ou symboles.
-    uppercase: Majuscules (ABC...)
-    lowercase: Minuscules (abc...)
-    numbers: Chiffres (123...)
-    symbols: Symboles (!-;...)
-    button:
-      copy: Copier
-      refresh: Rafraichir
-    copied: Le token a été copié
-    length: Longueur
-    tokenPlaceholder: Le token...
diff --git a/src/tools/token-generator/token-generator.service.ts b/src/tools/token-generator/token-generator.service.ts
index 3733a884..f928a415 100644
--- a/src/tools/token-generator/token-generator.service.ts
+++ b/src/tools/token-generator/token-generator.service.ts
@@ -20,7 +20,7 @@ export function createToken({
     withLowercase ? 'abcdefghijklmopqrstuvwxyz' : '',
     withNumbers ? '0123456789' : '',
     withSymbols ? '.,;:!?./-"\'#{([-|\\@)]=}*+' : '',
-  ].join(''); ;
+  ].join('');
 
   return shuffleString(allAlphabet.repeat(length)).substring(0, length);
 }
diff --git a/src/tools/toml-to-json/index.ts b/src/tools/toml-to-json/index.ts
index 653b432f..77a1b26e 100644
--- a/src/tools/toml-to-json/index.ts
+++ b/src/tools/toml-to-json/index.ts
@@ -1,11 +1,12 @@
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 import BracketIcon from '~icons/mdi/code-brackets';
 
 export const tool = defineTool({
-  name: 'TOML to JSON',
+  name: translate('tools.toml-to-json.title'),
   path: '/toml-to-json',
-  description: 'Parse and convert TOML to JSON.',
+  description: translate('tools.toml-to-json.description'),
   keywords: ['toml', 'json', 'convert', 'online', 'transform', 'parser'],
   component: () => import('./toml-to-json.vue'),
   icon: BracketIcon,
diff --git a/src/tools/toml-to-yaml/index.ts b/src/tools/toml-to-yaml/index.ts
index 69f9459c..2ee0958b 100644
--- a/src/tools/toml-to-yaml/index.ts
+++ b/src/tools/toml-to-yaml/index.ts
@@ -1,10 +1,11 @@
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 import BracketIcon from '~icons/mdi/code-brackets';
 
 export const tool = defineTool({
-  name: 'TOML to YAML',
+  name: translate('tools.toml-to-yaml.title'),
   path: '/toml-to-yaml',
-  description: 'Parse and convert TOML to YAML.',
+  description: translate('tools.toml-to-yaml.description'),
   keywords: ['toml', 'yaml', 'convert', 'online', 'transform', 'parse'],
   component: () => import('./toml-to-yaml.vue'),
   icon: BracketIcon,
diff --git a/src/tools/tools.store.ts b/src/tools/tools.store.ts
index d952b7cb..fb12450d 100644
--- a/src/tools/tools.store.ts
+++ b/src/tools/tools.store.ts
@@ -14,6 +14,7 @@ export const useToolStore = defineStore('tools', () => {
 
     return ({
       ...tool,
+      path: tool.path,
       name: t(`tools.${toolI18nKey}.title`, tool.name),
       description: t(`tools.${toolI18nKey}.description`, tool.description),
       category: t(`tools.categories.${tool.category.toLowerCase()}`, tool.category),
@@ -23,8 +24,9 @@ export const useToolStore = defineStore('tools', () => {
   const toolsByCategory = computed(() => {
     return _.chain(tools.value)
       .groupBy('category')
-      .map((components, name) => ({
+      .map((components, name, path) => ({
         name,
+        path,
         components,
       }))
       .value();
@@ -32,7 +34,7 @@ export const useToolStore = defineStore('tools', () => {
 
   const favoriteTools = computed(() => {
     return favoriteToolsName.value
-      .map(favoriteName => tools.value.find(({ name }) => name === favoriteName))
+      .map(favoriteName => tools.value.find(({ name, path }) => name === favoriteName || path === favoriteName))
       .filter(Boolean) as ToolWithCategory[]; // cast because .filter(Boolean) does not remove undefined from type
   });
 
@@ -43,15 +45,23 @@ export const useToolStore = defineStore('tools', () => {
     newTools: computed(() => tools.value.filter(({ isNew }) => isNew)),
 
     addToolToFavorites({ tool }: { tool: MaybeRef }) {
-      favoriteToolsName.value.push(get(tool).name);
+      const toolPath = get(tool).path;
+      if (toolPath) {
+        favoriteToolsName.value.push(toolPath);
+      }
     },
 
     removeToolFromFavorites({ tool }: { tool: MaybeRef }) {
-      favoriteToolsName.value = favoriteToolsName.value.filter(name => get(tool).name !== name);
+      favoriteToolsName.value = favoriteToolsName.value.filter(name => get(tool).name !== name && get(tool).path !== name);
     },
 
     isToolFavorite({ tool }: { tool: MaybeRef }) {
-      return favoriteToolsName.value.includes(get(tool).name);
+      return favoriteToolsName.value.includes(get(tool).name)
+        || favoriteToolsName.value.includes(get(tool).path);
+    },
+
+    updateFavoriteTools(newOrder: ToolWithCategory[]) {
+      favoriteToolsName.value = newOrder.map(tool => tool.path);
     },
   };
 });
diff --git a/src/tools/ulid-generator/index.ts b/src/tools/ulid-generator/index.ts
index 6a5408dd..c12679a7 100644
--- a/src/tools/ulid-generator/index.ts
+++ b/src/tools/ulid-generator/index.ts
@@ -1,10 +1,11 @@
 import { SortDescendingNumbers } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'ULID generator',
+  name: translate('tools.ulid-generator.title'),
   path: '/ulid-generator',
-  description: 'Generate random Universally Unique Lexicographically Sortable Identifier (ULID).',
+  description: translate('tools.ulid-generator.description'),
   keywords: ['ulid', 'generator', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
   component: () => import('./ulid-generator.vue'),
   icon: SortDescendingNumbers,
diff --git a/src/tools/url-encoder/index.ts b/src/tools/url-encoder/index.ts
index bd19b890..ab85118c 100644
--- a/src/tools/url-encoder/index.ts
+++ b/src/tools/url-encoder/index.ts
@@ -1,10 +1,11 @@
 import { Link } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Encode/decode url formatted strings',
+  name: translate('tools.url-encoder.title'),
   path: '/url-encoder',
-  description: 'Encode to url-encoded format (also known as "percent-encoded") or decode from it.',
+  description: translate('tools.url-encoder.description'),
   keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'],
   component: () => import('./url-encoder.vue'),
   icon: Link,
diff --git a/src/tools/url-encoder/url-encoder.vue b/src/tools/url-encoder/url-encoder.vue
index c43f8193..19025190 100644
--- a/src/tools/url-encoder/url-encoder.vue
+++ b/src/tools/url-encoder/url-encoder.vue
@@ -23,7 +23,7 @@ const decodeInput = ref('Hello%20world%20%3A)');
 const decodeOutput = computed(() => withDefaultOnError(() => decodeURIComponent(decodeInput.value), ''));
 
 const decodeValidation = useValidation({
-  source: encodeInput,
+  source: decodeInput,
   rules: [
     {
       validator: value => isNotThrowing(() => decodeURIComponent(value)),
diff --git a/src/tools/url-parser/index.ts b/src/tools/url-parser/index.ts
index d1c8dfe8..77976a24 100644
--- a/src/tools/url-parser/index.ts
+++ b/src/tools/url-parser/index.ts
@@ -1,11 +1,11 @@
 import { Unlink } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'Url parser',
+  name: translate('tools.url-parser.title'),
   path: '/url-parser',
-  description:
-    'Parse an url string to get all the different parts (protocol, origin, params, port, username-password, ...)',
+  description: translate('tools.url-parser.description'),
   keywords: ['url', 'parser', 'protocol', 'origin', 'params', 'port', 'username', 'password', 'href'],
   component: () => import('./url-parser.vue'),
   icon: Unlink,
diff --git a/src/tools/user-agent-parser/index.ts b/src/tools/user-agent-parser/index.ts
index 1ae05d14..4d026145 100644
--- a/src/tools/user-agent-parser/index.ts
+++ b/src/tools/user-agent-parser/index.ts
@@ -1,10 +1,11 @@
 import { Browser } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'User-agent parser',
+  name: translate('tools.user-agent-parser.title'),
   path: '/user-agent-parser',
-  description: 'Detect and parse Browser, Engine, OS, CPU, and Device type/model from an user-agent string.',
+  description: translate('tools.user-agent-parser.description'),
   keywords: ['user', 'agent', 'parser', 'browser', 'engine', 'os', 'cpu', 'device', 'user-agent', 'client'],
   component: () => import('./user-agent-parser.vue'),
   icon: Browser,
diff --git a/src/tools/uuid-generator/index.ts b/src/tools/uuid-generator/index.ts
index 9289b902..54ec479f 100644
--- a/src/tools/uuid-generator/index.ts
+++ b/src/tools/uuid-generator/index.ts
@@ -1,11 +1,11 @@
 import { Fingerprint } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'UUIDs generator',
+  name: translate('tools.uuid-generator.title'),
   path: '/uuid-generator',
-  description:
-    'A Universally Unique Identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot!).',
+  description: translate('tools.uuid-generator.description'),
   keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique', 'v1', 'v3', 'v5', 'nil'],
   component: () => import('./uuid-generator.vue'),
   icon: Fingerprint,
diff --git a/src/tools/wifi-qr-code-generator/index.ts b/src/tools/wifi-qr-code-generator/index.ts
index ad0135c3..b59b95df 100644
--- a/src/tools/wifi-qr-code-generator/index.ts
+++ b/src/tools/wifi-qr-code-generator/index.ts
@@ -1,11 +1,11 @@
 import { Qrcode } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'WiFi QR Code generator',
+  name: translate('tools.wifi-qrcode-generator.title'),
   path: '/wifi-qrcode-generator',
-  description:
-    'Generate and download QR-codes for quick connections to WiFi networks.',
+  description: translate('tools.wifi-qrcode-generator.description'),
   keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent', 'wifi'],
   component: () => import('./wifi-qr-code-generator.vue'),
   icon: Qrcode,
diff --git a/src/tools/xml-formatter/index.ts b/src/tools/xml-formatter/index.ts
index fe28d3ae..7aa096da 100644
--- a/src/tools/xml-formatter/index.ts
+++ b/src/tools/xml-formatter/index.ts
@@ -1,10 +1,11 @@
 import { Code } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'XML formatter',
+  name: translate('tools.xml-formatter.title'),
   path: '/xml-formatter',
-  description: 'Prettify your XML string to a human friendly readable format.',
+  description: translate('tools.xml-formatter.description'),
   keywords: ['xml', 'prettify', 'format'],
   component: () => import('./xml-formatter.vue'),
   icon: Code,
diff --git a/src/tools/xml-to-json/index.ts b/src/tools/xml-to-json/index.ts
new file mode 100644
index 00000000..8d83f4fe
--- /dev/null
+++ b/src/tools/xml-to-json/index.ts
@@ -0,0 +1,12 @@
+import { Braces } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+  name: 'XML to JSON',
+  path: '/xml-to-json',
+  description: 'Convert XML to JSON',
+  keywords: ['xml', 'json'],
+  component: () => import('./xml-to-json.vue'),
+  icon: Braces,
+  createdAt: new Date('2024-08-09'),
+});
diff --git a/src/tools/xml-to-json/xml-to-json.vue b/src/tools/xml-to-json/xml-to-json.vue
new file mode 100644
index 00000000..e1e5a477
--- /dev/null
+++ b/src/tools/xml-to-json/xml-to-json.vue
@@ -0,0 +1,32 @@
+
+
+
+  
+
diff --git a/src/tools/yaml-to-json-converter/index.ts b/src/tools/yaml-to-json-converter/index.ts
index 724ecdb7..60110f09 100644
--- a/src/tools/yaml-to-json-converter/index.ts
+++ b/src/tools/yaml-to-json-converter/index.ts
@@ -1,10 +1,11 @@
 import { AlignJustified } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'YAML to JSON converter',
+  name: translate('tools.yaml-to-json-converter.title'),
   path: '/yaml-to-json-converter',
-  description: 'Simply convert YAML to JSON with this live online converter.',
+  description: translate('tools.yaml-to-json-converter.description'),
   keywords: ['yaml', 'to', 'json'],
   component: () => import('./yaml-to-json.vue'),
   icon: AlignJustified,
diff --git a/src/tools/yaml-to-json-converter/yaml-to-json.e2e.spec.ts b/src/tools/yaml-to-json-converter/yaml-to-json.e2e.spec.ts
index 7b2a2d18..d6ed84c3 100644
--- a/src/tools/yaml-to-json-converter/yaml-to-json.e2e.spec.ts
+++ b/src/tools/yaml-to-json-converter/yaml-to-json.e2e.spec.ts
@@ -28,4 +28,53 @@ test.describe('Tool - Yaml to json', () => {
    `.trim(),
     );
   });
+
+  test('Yaml is parsed with merge key and output correct json', async ({ page }) => {
+    await page.getByTestId('input').fill(`
+      default: &default
+        name: ''
+        age: 0
+
+      person:
+        *default
+
+      persons:
+      - <<: *default
+        age: 1
+      - <<: *default
+        name: John
+      - { age: 3, <<: *default }
+      
+      `);
+
+    const generatedJson = await page.getByTestId('area-content').innerText();
+
+    expect(generatedJson.trim()).toEqual(
+      `
+{
+   "default": {
+      "name": "",
+      "age": 0
+   },
+   "person": {
+      "name": "",
+      "age": 0
+   },
+   "persons": [
+      {
+         "name": "",
+         "age": 1
+      },
+      {
+         "name": "John",
+         "age": 0
+      },
+      {
+         "age": 3,
+         "name": ""
+      }
+   ]
+}`.trim(),
+    );
+  });
 });
diff --git a/src/tools/yaml-to-json-converter/yaml-to-json.vue b/src/tools/yaml-to-json-converter/yaml-to-json.vue
index 39c9297f..72608add 100644
--- a/src/tools/yaml-to-json-converter/yaml-to-json.vue
+++ b/src/tools/yaml-to-json-converter/yaml-to-json.vue
@@ -6,7 +6,7 @@ import { withDefaultOnError } from '@/utils/defaults';
 
 function transformer(value: string) {
   return withDefaultOnError(() => {
-    const obj = parseYaml(value);
+    const obj = parseYaml(value, { merge: true });
     return obj ? JSON.stringify(obj, null, 3) : '';
   }, '');
 }
diff --git a/src/tools/yaml-to-toml/index.ts b/src/tools/yaml-to-toml/index.ts
index b6a7077f..d788887e 100644
--- a/src/tools/yaml-to-toml/index.ts
+++ b/src/tools/yaml-to-toml/index.ts
@@ -1,10 +1,11 @@
 import { AlignJustified } from '@vicons/tabler';
 import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
 
 export const tool = defineTool({
-  name: 'YAML to TOML',
+  name: translate('tools.yaml-to-toml.title'),
   path: '/yaml-to-toml',
-  description: 'Parse and convert YAML to TOML.',
+  description: translate('tools.yaml-to-toml.description'),
   keywords: ['yaml', 'to', 'toml', 'convert', 'transform'],
   component: () => import('./yaml-to-toml.vue'),
   icon: AlignJustified,
diff --git a/src/tools/yaml-viewer/index.ts b/src/tools/yaml-viewer/index.ts
new file mode 100644
index 00000000..f3043270
--- /dev/null
+++ b/src/tools/yaml-viewer/index.ts
@@ -0,0 +1,13 @@
+import { AlignJustified } from '@vicons/tabler';
+import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
+
+export const tool = defineTool({
+  name: translate('tools.yaml-prettify.title'),
+  path: '/yaml-prettify',
+  description: translate('tools.yaml-prettify.description'),
+  keywords: ['yaml', 'viewer', 'prettify', 'format'],
+  component: () => import('./yaml-viewer.vue'),
+  icon: AlignJustified,
+  createdAt: new Date('2024-01-31'),
+});
diff --git a/src/tools/yaml-viewer/yaml-models.ts b/src/tools/yaml-viewer/yaml-models.ts
new file mode 100644
index 00000000..54569db8
--- /dev/null
+++ b/src/tools/yaml-viewer/yaml-models.ts
@@ -0,0 +1,24 @@
+import { type MaybeRef, get } from '@vueuse/core';
+
+import yaml from 'yaml';
+
+export { formatYaml };
+
+function formatYaml({
+  rawYaml,
+  sortKeys = false,
+  indentSize = 2,
+}: {
+  rawYaml: MaybeRef
+  sortKeys?: MaybeRef
+  indentSize?: MaybeRef
+}) {
+  const parsedYaml = yaml.parse(get(rawYaml));
+
+  const formattedYAML = yaml.stringify(parsedYaml, {
+    sortMapEntries: get(sortKeys),
+    indent: get(indentSize),
+  });
+
+  return formattedYAML;
+}
diff --git a/src/tools/yaml-viewer/yaml-viewer.vue b/src/tools/yaml-viewer/yaml-viewer.vue
new file mode 100644
index 00000000..3385eee2
--- /dev/null
+++ b/src/tools/yaml-viewer/yaml-viewer.vue
@@ -0,0 +1,72 @@
+
+
+
+  
+
+  
+    
+  
+  
+    
+  
+
+
+
diff --git a/src/utils/base64.test.ts b/src/utils/base64.test.ts
index 994f1b1b..51d15239 100644
--- a/src/utils/base64.test.ts
+++ b/src/utils/base64.test.ts
@@ -38,7 +38,8 @@ describe('base64 utils', () => {
 
     it('should throw for incorrect base64 string', () => {
       expect(() => base64ToText('a')).to.throw('Incorrect base64 string');
-      expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
+      // should not really be false because trimming of space is now implied
+      // expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
       expect(() => base64ToText('é')).to.throw('Incorrect base64 string');
       // missing final '='
       expect(() => base64ToText('bG9yZW0gaXBzdW0')).to.throw('Incorrect base64 string');
@@ -56,17 +57,17 @@ describe('base64 utils', () => {
 
     it('should return false for incorrect base64 string', () => {
       expect(isValidBase64('a')).to.eql(false);
-      expect(isValidBase64(' ')).to.eql(false);
       expect(isValidBase64('é')).to.eql(false);
       expect(isValidBase64('data:text/plain;notbase64,YQ==')).to.eql(false);
       // missing final '='
       expect(isValidBase64('bG9yZW0gaXBzdW0')).to.eql(false);
     });
 
-    it('should return false for untrimmed correct base64 string', () => {
-      expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(false);
-      expect(isValidBase64(' LTE=')).to.eql(false);
-      expect(isValidBase64(' YQ== ')).to.eql(false);
+    it('should return true for untrimmed correct base64 string', () => {
+      expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(true);
+      expect(isValidBase64(' LTE=')).to.eql(true);
+      expect(isValidBase64(' YQ== ')).to.eql(true);
+      expect(isValidBase64(' ')).to.eql(true);
     });
   });
 
diff --git a/src/utils/base64.ts b/src/utils/base64.ts
index 16912ee3..44e59f41 100644
--- a/src/utils/base64.ts
+++ b/src/utils/base64.ts
@@ -1,7 +1,9 @@
+import { Base64 } from 'js-base64';
+
 export { textToBase64, base64ToText, isValidBase64, removePotentialDataAndMimePrefix };
 
 function textToBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
-  const encoded = window.btoa(str);
+  const encoded = Base64.encode(str);
   return makeUrlSafe ? makeUriSafe(encoded) : encoded;
 }
 
@@ -16,7 +18,7 @@ function base64ToText(str: string, { makeUrlSafe = false }: { makeUrlSafe?: bool
   }
 
   try {
-    return window.atob(cleanStr);
+    return Base64.decode(cleanStr);
   }
   catch (_) {
     throw new Error('Incorrect base64 string');
@@ -34,10 +36,11 @@ function isValidBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boo
   }
 
   try {
+    const reEncodedBase64 = Base64.fromUint8Array(Base64.toUint8Array(cleanStr));
     if (makeUrlSafe) {
-      return removePotentialPadding(window.btoa(window.atob(cleanStr))) === cleanStr;
+      return removePotentialPadding(reEncodedBase64) === cleanStr;
     }
-    return window.btoa(window.atob(cleanStr)) === cleanStr;
+    return reEncodedBase64 === cleanStr.replace(/\s/g, '');
   }
   catch (err) {
     return false;
diff --git a/vite.config.ts b/vite.config.ts
index 00f90c33..42a2cb29 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,20 +1,20 @@
-import { URL, fileURLToPath } from 'node:url';
 import { resolve } from 'node:path';
+import { URL, fileURLToPath } from 'node:url';
 
-import { defineConfig } from 'vite';
+import VueI18n from '@intlify/unplugin-vue-i18n/vite';
 import vue from '@vitejs/plugin-vue';
 import vueJsx from '@vitejs/plugin-vue-jsx';
+import Unocss from 'unocss/vite';
+import AutoImport from 'unplugin-auto-import/vite';
+import IconsResolver from 'unplugin-icons/resolver';
+import Icons from 'unplugin-icons/vite';
+import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
+import Components from 'unplugin-vue-components/vite';
+import { defineConfig } from 'vite';
+import { VitePWA } from 'vite-plugin-pwa';
 import markdown from 'vite-plugin-vue-markdown';
 import svgLoader from 'vite-svg-loader';
-import { VitePWA } from 'vite-plugin-pwa';
-import AutoImport from 'unplugin-auto-import/vite';
-import Components from 'unplugin-vue-components/vite';
-import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
-import Unocss from 'unocss/vite';
 import { configDefaults } from 'vitest/config';
-import Icons from 'unplugin-icons/vite';
-import IconsResolver from 'unplugin-icons/resolver';
-import VueI18n from '@intlify/unplugin-vue-i18n/vite';
 
 const baseUrl = process.env.BASE_URL ?? '/';
 
@@ -23,9 +23,13 @@ export default defineConfig({
   plugins: [
     VueI18n({
       runtimeOnly: true,
+      jitCompilation: true,
       compositionOnly: true,
       fullInstall: true,
-      include: [resolve(__dirname, 'locales/**')],
+      strictMessage: false,
+      include: [
+        resolve(__dirname, 'locales/**'),
+      ],
     }),
     AutoImport({
       imports: [ |