+
+ 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 52bdf8e3..388cfaf4 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -1,6 +1,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 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';
@@ -75,6 +86,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[] = [
{
@@ -93,6 +105,7 @@ export const toolsByCategory: ToolCategory[] = [
caseConverter,
textToNatoAlphabet,
textToBinary,
+ textToUnicode,
yamlToJson,
yamlToToml,
jsonToYaml,
@@ -100,6 +113,9 @@ export const toolsByCategory: ToolCategory[] = [
listConverter,
tomlToJson,
tomlToYaml,
+ xmlToJson,
+ jsonToXml,
+ markdownToHtml,
],
},
{
@@ -120,6 +136,7 @@ export const toolsByCategory: ToolCategory[] = [
userAgentParser,
httpStatusCodes,
jsonDiff,
+ safelinkDecoder,
],
},
{
@@ -139,6 +156,10 @@ export const toolsByCategory: ToolCategory[] = [
chmodCalculator,
dockerRunToDockerComposeConverter,
xmlFormatter,
+ yamlViewer,
+ emailNormalizer,
+ regexTester,
+ regexMemo,
],
},
{
@@ -155,7 +176,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.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 @@
+
+
+
+
+
+
+
+ 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/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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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/unocss.config.ts b/unocss.config.ts
index e115c092..14ee6940 100644
--- a/unocss.config.ts
+++ b/unocss.config.ts
@@ -10,7 +10,7 @@ import {
import { presetScrollbar } from 'unocss-preset-scrollbar';
export default defineConfig({
- presets: [presetUno(), presetAttributify(), presetTypography(), presetScrollbar()],
+ presets: [presetUno(), presetAttributify({ ignoreAttributes: ['size'] }), presetTypography(), presetScrollbar()],
transformers: [transformerDirectives(), transformerVariantGroup()],
theme: {
colors: {
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: [
|