+  
     
-      
-        
-          
-            {{ $t('home.follow.p1') }}
-            GitHub
-            {{ $t('home.follow.p2') }}
-            Twitter.
-            {{ $t('home.follow.thankYou') }}
-            
-          
-        
-      
+      
+        
+          {{ $t('home.follow.p1') }}
+          GitHub
+          {{ $t('home.follow.p2') }}
+          Twitter.
+          {{ $t('home.follow.thankYou') }}
+          
+        
+      
-          
{{ $t('home.categories.favoriteTools') }}
-          
-            
-              
-            
-          
+          
+            {{ $t('home.categories.favoriteTools') }}
+          
+          
+            
+          
         
-        
{{ t('home.categories.newestTools') }}
-        
-          
-            
-          
-        
+        
+          {{ t('home.categories.newestTools') }}
+        
+        
+          
+        
       
 
-      
{{ $t('home.categories.allTools') }}
-      
-        
-          
-            
-          
-        
-      
+      
+        {{ $t('home.categories.allTools') }}
+      
+      
+        
+      
      
 
 
 
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 @@
+
+
+
+  
+    
+      
+      
+        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
++ | 
+        
+        
+          
+            | {{ match.index }}+ | {{ match.value }}+ | + +
+                +
+                  "{{ capture.name }}" = {{ capture.value }} [{{ capture.start }} - {{ capture.end }}]
+                + | + +
+                +
+                  "{{ group.name }}" = {{ group.value }} [{{ group.start }} - {{ group.end }}]
+                + | 
+        
+      
+      
+        No match
+      
+    
+
+    
+      {{ 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/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-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: [