- About
-
- This wonderful website, made with ❤ by
-
- Corentin Thomasset
- ,
- aggregates useful tools for developer and people working in IT. If you find it useful, please feel free to share
- it to people you think may find it useful too and don't forget to bookmark it in your shortcut bar!
-
-
- IT Tools is open-source (under the MIT license) and free, and will always be, but it costs me money to host and
- renew the domain name. If you want to support my work, and encourage me to add more tools, please consider
- supporting by
- tracker.trackEvent({ eventName: 'Support button clicked' })"
- >
- sponsoring me
- .
-
-
- Technologies
-
- IT Tools is made in Vue.js (Vue 3) with the the Naive UI component library and is hosted and continuously deployed
- by Vercel. Third-party open-source libraries are used in some tools, you may find the complete list in the
-
- package.json
-
- file of the repository.
-
-
- Found a bug? A tool is missing?
-
- If you need a tool that is currently not present here, and you think can be useful, you are welcome to submit a
- feature request in the
-
- issues section
-
- in the GitHub repository.
-
-
- And if you found a bug, or something doesn't work as expected, please file a bug report in the
-
- issues section
-
- in the GitHub repository.
-
-
-
-
+
+
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/date-time-converter.e2e.spec.ts b/src/tools/date-time-converter/date-time-converter.e2e.spec.ts
index 34ee7495..249dd754 100644
--- a/src/tools/date-time-converter/date-time-converter.e2e.spec.ts
+++ b/src/tools/date-time-converter/date-time-converter.e2e.spec.ts
@@ -29,5 +29,6 @@ test.describe('Date time converter - json to yaml', () => {
expect((await page.getByTestId('Timestamp').inputValue()).trim()).toEqual('1681333824000');
expect((await page.getByTestId('UTC format').inputValue()).trim()).toEqual('Wed, 12 Apr 2023 21:10:24 GMT');
expect((await page.getByTestId('Mongo ObjectID').inputValue()).trim()).toEqual('64371e400000000000000000');
+ expect((await page.getByTestId('Excel date/time').inputValue()).trim()).toEqual('45028.88222222222');
});
});
diff --git a/src/tools/date-time-converter/date-time-converter.models.test.ts b/src/tools/date-time-converter/date-time-converter.models.test.ts
index 502cdc67..c2c7bee9 100644
--- a/src/tools/date-time-converter/date-time-converter.models.test.ts
+++ b/src/tools/date-time-converter/date-time-converter.models.test.ts
@@ -1,5 +1,8 @@
import { describe, expect, test } from 'vitest';
import {
+ dateToExcelFormat,
+ excelFormatToDate,
+ isExcelFormat,
isISO8601DateTimeString,
isISO9075DateString,
isMongoObjectId,
@@ -139,4 +142,39 @@ describe('date-time-converter models', () => {
expect(isMongoObjectId('')).toBe(false);
});
});
+
+ describe('isExcelFormat', () => {
+ test('an Excel format string is a floating number that can be negative', () => {
+ expect(isExcelFormat('0')).toBe(true);
+ expect(isExcelFormat('1')).toBe(true);
+ expect(isExcelFormat('1.1')).toBe(true);
+ expect(isExcelFormat('-1.1')).toBe(true);
+ expect(isExcelFormat('-1')).toBe(true);
+
+ expect(isExcelFormat('')).toBe(false);
+ expect(isExcelFormat('foo')).toBe(false);
+ expect(isExcelFormat('1.1.1')).toBe(false);
+ });
+ });
+
+ describe('dateToExcelFormat', () => {
+ test('a date in Excel format is the number of days since 01/01/1900', () => {
+ expect(dateToExcelFormat(new Date('2016-05-20T00:00:00.000Z'))).toBe('42510');
+ expect(dateToExcelFormat(new Date('2016-05-20T12:00:00.000Z'))).toBe('42510.5');
+ expect(dateToExcelFormat(new Date('2023-10-31T09:26:06.421Z'))).toBe('45230.39312987268');
+ expect(dateToExcelFormat(new Date('1970-01-01T00:00:00.000Z'))).toBe('25569');
+ expect(dateToExcelFormat(new Date('1800-01-01T00:00:00.000Z'))).toBe('-36522');
+ });
+ });
+
+ describe('excelFormatToDate', () => {
+ test('a date in Excel format is the number of days since 01/01/1900', () => {
+ expect(excelFormatToDate('0')).toEqual(new Date('1899-12-30T00:00:00.000Z'));
+ expect(excelFormatToDate('1')).toEqual(new Date('1899-12-31T00:00:00.000Z'));
+ expect(excelFormatToDate('2')).toEqual(new Date('1900-01-01T00:00:00.000Z'));
+ expect(excelFormatToDate('4242.4242')).toEqual(new Date('1911-08-12T10:10:50.880Z'));
+ expect(excelFormatToDate('42738.22626859954')).toEqual(new Date('2017-01-03T05:25:49.607Z'));
+ expect(excelFormatToDate('-1000')).toEqual(new Date('1897-04-04T00:00:00.000Z'));
+ });
+ });
});
diff --git a/src/tools/date-time-converter/date-time-converter.models.ts b/src/tools/date-time-converter/date-time-converter.models.ts
index 173b8a87..f5eedbfa 100644
--- a/src/tools/date-time-converter/date-time-converter.models.ts
+++ b/src/tools/date-time-converter/date-time-converter.models.ts
@@ -9,6 +9,9 @@ export {
isTimestamp,
isUTCDateString,
isMongoObjectId,
+ dateToExcelFormat,
+ excelFormatToDate,
+ isExcelFormat,
};
const ISO8601_REGEX
@@ -21,6 +24,8 @@ const RFC3339_REGEX
const RFC7231_REGEX = /^[A-Za-z]{3},\s[0-9]{2}\s[A-Za-z]{3}\s[0-9]{4}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\sGMT$/;
+const EXCEL_FORMAT_REGEX = /^-?\d+(\.\d+)?$/;
+
function createRegexMatcher(regex: RegExp) {
return (date?: string) => !_.isNil(date) && regex.test(date);
}
@@ -33,6 +38,8 @@ const isUnixTimestamp = createRegexMatcher(/^[0-9]{1,10}$/);
const isTimestamp = createRegexMatcher(/^[0-9]{1,13}$/);
const isMongoObjectId = createRegexMatcher(/^[0-9a-fA-F]{24}$/);
+const isExcelFormat = createRegexMatcher(EXCEL_FORMAT_REGEX);
+
function isUTCDateString(date?: string) {
if (_.isNil(date)) {
return false;
@@ -45,3 +52,11 @@ function isUTCDateString(date?: string) {
return false;
}
}
+
+function dateToExcelFormat(date: Date) {
+ return String(((date.getTime()) / (1000 * 60 * 60 * 24)) + 25569);
+}
+
+function excelFormatToDate(excelFormat: string | number) {
+ return new Date((Number(excelFormat) - 25569) * 86400 * 1000);
+}
diff --git a/src/tools/date-time-converter/date-time-converter.vue b/src/tools/date-time-converter/date-time-converter.vue
index 88fa6012..5636ed46 100644
--- a/src/tools/date-time-converter/date-time-converter.vue
+++ b/src/tools/date-time-converter/date-time-converter.vue
@@ -14,6 +14,9 @@ import {
} from 'date-fns';
import type { DateFormat, ToDateMapper } from './date-time-converter.types';
import {
+ dateToExcelFormat,
+ excelFormatToDate,
+ isExcelFormat,
isISO8601DateTimeString,
isISO9075DateString,
isMongoObjectId,
@@ -85,6 +88,12 @@ const formats: DateFormat[] = [
toDate: objectId => new Date(Number.parseInt(objectId.substring(0, 8), 16) * 1000),
formatMatcher: date => isMongoObjectId(date),
},
+ {
+ name: 'Excel date/time',
+ fromDate: date => dateToExcelFormat(date),
+ toDate: excelFormatToDate,
+ formatMatcher: isExcelFormat,
+ },
];
const formatIndex = ref(6);
@@ -146,7 +155,7 @@ function formatDateUsingFormatter(formatter: (date: Date) => string, date?: Date
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/encryption.vue b/src/tools/encryption/encryption.vue
index 4a348f85..2ad47b51 100644
--- a/src/tools/encryption/encryption.vue
+++ b/src/tools/encryption/encryption.vue
@@ -1,5 +1,6 @@
@@ -63,7 +65,11 @@ const decryptOutput = computed(() =>
/>
+
+ {{ decryptError }}
+ import('./encryption.vue'),
icon: Lock,
diff --git a/src/tools/eta-calculator/eta-calculator.vue b/src/tools/eta-calculator/eta-calculator.vue
index a81a77de..e1c36a62 100644
--- a/src/tools/eta-calculator/eta-calculator.vue
+++ b/src/tools/eta-calculator/eta-calculator.vue
@@ -26,8 +26,8 @@ const endAt = computed(() =>
- With a concrete example, if you wash 3 plates in 5 minutes and you have 500 plates to wash, it will take you 5
- hours and 10 minutes to wash them all.
+ With a concrete example, if you wash 5 plates in 3 minutes and you have 500 plates to wash, it will take you 5
+ hours to wash them all.
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-item.vue b/src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue
index 9a4cf1bd..5be23292 100644
--- a/src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue
+++ b/src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue
@@ -6,13 +6,9 @@ const { icon, title, action, isActive } = toRefs(props);
-
-
-
-
-
-
-
- {{ title }}
-
+
+
+
+
+
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/iban-validator-and-parser.e2e.spec.ts b/src/tools/iban-validator-and-parser/iban-validator-and-parser.e2e.spec.ts
new file mode 100644
index 00000000..c4a99860
--- /dev/null
+++ b/src/tools/iban-validator-and-parser/iban-validator-and-parser.e2e.spec.ts
@@ -0,0 +1,52 @@
+import { type Page, expect, test } from '@playwright/test';
+
+async function extractIbanInfo({ page }: { page: Page }) {
+ const itemsLines = await page
+ .locator('.c-key-value-list__item').all();
+
+ return await Promise.all(
+ itemsLines.map(async item => [
+ (await item.locator('.c-key-value-list__key').textContent() ?? '').trim(),
+ (await item.locator('.c-key-value-list__value').textContent() ?? '').trim(),
+ ]),
+ );
+}
+
+test.describe('Tool - Iban validator and parser', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/iban-validator-and-parser');
+ });
+
+ test('Has correct title', async ({ page }) => {
+ await expect(page).toHaveTitle('IBAN validator and parser - IT Tools');
+ });
+
+ test('iban info are extracted from a valid iban', async ({ page }) => {
+ await page.getByTestId('iban-input').fill('DE89370400440532013000');
+
+ const ibanInfo = await extractIbanInfo({ page });
+
+ expect(ibanInfo).toEqual([
+ ['Is IBAN valid ?', 'Yes'],
+ ['Is IBAN a QR-IBAN ?', 'No'],
+ ['Country code', 'DE'],
+ ['BBAN', '370400440532013000'],
+ ['IBAN friendly format', 'DE89 3704 0044 0532 0130 00'],
+ ]);
+ });
+
+ test('invalid iban errors are displayed', async ({ page }) => {
+ await page.getByTestId('iban-input').fill('FR7630006060011234567890189');
+
+ const ibanInfo = await extractIbanInfo({ page });
+
+ expect(ibanInfo).toEqual([
+ ['Is IBAN valid ?', 'No'],
+ ['IBAN errors', 'Wrong account bank branch checksum Wrong IBAN checksum'],
+ ['Is IBAN a QR-IBAN ?', 'No'],
+ ['Country code', 'N/A'],
+ ['BBAN', 'N/A'],
+ ['IBAN friendly format', 'FR76 3000 6060 0112 3456 7890 189'],
+ ]);
+ });
+});
diff --git a/src/tools/iban-validator-and-parser/iban-validator-and-parser.service.ts b/src/tools/iban-validator-and-parser/iban-validator-and-parser.service.ts
new file mode 100644
index 00000000..bde71dba
--- /dev/null
+++ b/src/tools/iban-validator-and-parser/iban-validator-and-parser.service.ts
@@ -0,0 +1,18 @@
+import { ValidationErrorsIBAN } from 'ibantools';
+
+export { getFriendlyErrors };
+
+const ibanErrorToMessage = {
+ [ValidationErrorsIBAN.NoIBANProvided]: 'No IBAN provided',
+ [ValidationErrorsIBAN.NoIBANCountry]: 'No IBAN country',
+ [ValidationErrorsIBAN.WrongBBANLength]: 'Wrong BBAN length',
+ [ValidationErrorsIBAN.WrongBBANFormat]: 'Wrong BBAN format',
+ [ValidationErrorsIBAN.ChecksumNotNumber]: 'Checksum is not a number',
+ [ValidationErrorsIBAN.WrongIBANChecksum]: 'Wrong IBAN checksum',
+ [ValidationErrorsIBAN.WrongAccountBankBranchChecksum]: 'Wrong account bank branch checksum',
+ [ValidationErrorsIBAN.QRIBANNotAllowed]: 'QR-IBAN not allowed',
+};
+
+function getFriendlyErrors(errorCodes: ValidationErrorsIBAN[]) {
+ return errorCodes.map(errorCode => ibanErrorToMessage[errorCode]).filter(Boolean);
+}
diff --git a/src/tools/iban-validator-and-parser/iban-validator-and-parser.vue b/src/tools/iban-validator-and-parser/iban-validator-and-parser.vue
new file mode 100644
index 00000000..6844dc5a
--- /dev/null
+++ b/src/tools/iban-validator-and-parser/iban-validator-and-parser.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/iban-validator-and-parser/index.ts b/src/tools/iban-validator-and-parser/index.ts
new file mode 100644
index 00000000..ff7ff135
--- /dev/null
+++ b/src/tools/iban-validator-and-parser/index.ts
@@ -0,0 +1,13 @@
+import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
+import Bank from '~icons/mdi/bank';
+
+export const tool = defineTool({
+ name: translate('tools.iban-validator-and-parser.title'),
+ path: '/iban-validator-and-parser',
+ description: translate('tools.iban-validator-and-parser.description'),
+ keywords: ['iban', 'validator', 'and', 'parser', 'bic', 'bank'],
+ component: () => import('./iban-validator-and-parser.vue'),
+ icon: Bank,
+ createdAt: new Date('2023-08-26'),
+});
diff --git a/src/tools/index.ts b/src/tools/index.ts
index c5686a48..388cfaf4 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -1,7 +1,25 @@
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';
+import { tool as textToBinary } from './text-to-binary';
+import { tool as ulidGenerator } from './ulid-generator';
+import { tool as ibanValidatorAndParser } from './iban-validator-and-parser';
import { tool as stringObfuscator } from './string-obfuscator';
+import { tool as textDiff } from './text-diff';
import { tool as emojiPicker } from './emoji-picker';
import { tool as passwordStrengthAnalyser } from './password-strength-analyser';
import { tool as yamlToToml } from './yaml-to-toml';
@@ -54,6 +72,7 @@ import { tool as metaTagGenerator } from './meta-tag-generator';
import { tool as mimeTypes } from './mime-types';
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
import { tool as qrCodeGenerator } from './qr-code-generator';
+import { tool as wifiQrCodeGenerator } from './wifi-qr-code-generator';
import { tool as randomPortGenerator } from './random-port-generator';
import { tool as romanNumeralConverter } from './roman-numeral-converter';
import { tool as sqlPrettify } from './sql-prettify';
@@ -67,11 +86,12 @@ 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[] = [
{
name: 'Crypto',
- components: [tokenGenerator, hashText, bcrypt, uuidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser],
+ components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker],
},
{
name: 'Converter',
@@ -84,6 +104,8 @@ export const toolsByCategory: ToolCategory[] = [
colorConverter,
caseConverter,
textToNatoAlphabet,
+ textToBinary,
+ textToUnicode,
yamlToJson,
yamlToToml,
jsonToYaml,
@@ -91,6 +113,9 @@ export const toolsByCategory: ToolCategory[] = [
listConverter,
tomlToJson,
tomlToYaml,
+ xmlToJson,
+ jsonToXml,
+ markdownToHtml,
],
},
{
@@ -111,11 +136,12 @@ export const toolsByCategory: ToolCategory[] = [
userAgentParser,
httpStatusCodes,
jsonDiff,
+ safelinkDecoder,
],
},
{
name: 'Images and videos',
- components: [qrCodeGenerator, svgPlaceholderGenerator, cameraRecorder],
+ components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder],
},
{
name: 'Development',
@@ -130,11 +156,15 @@ export const toolsByCategory: ToolCategory[] = [
chmodCalculator,
dockerRunToDockerComposeConverter,
xmlFormatter,
+ yamlViewer,
+ emailNormalizer,
+ regexTester,
+ regexMemo,
],
},
{
name: 'Network',
- components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, ipv6UlaGenerator],
+ components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator],
},
{
name: 'Math',
@@ -146,11 +176,19 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Text',
- components: [loremIpsumGenerator, textStatistics, emojiPicker, stringObfuscator],
+ components: [
+ loremIpsumGenerator,
+ textStatistics,
+ emojiPicker,
+ stringObfuscator,
+ textDiff,
+ numeronymGenerator,
+ asciiTextDrawer,
+ ],
},
{
name: 'Data',
- components: [phoneParserAndFormatter],
+ components: [phoneParserAndFormatter, ibanValidatorAndParser],
},
];
diff --git a/src/tools/integer-base-converter/index.ts b/src/tools/integer-base-converter/index.ts
index 0008568c..f60d996d 100644
--- a/src/tools/integer-base-converter/index.ts
+++ b/src/tools/integer-base-converter/index.ts
@@ -1,10 +1,11 @@
import { ArrowsLeftRight } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'Integer base converter',
+ name: translate('tools.base-converter.title'),
path: '/base-converter',
- description: 'Convert number between different bases (decimal, hexadecimal, binary, octal, base64, ...)',
+ description: translate('tools.base-converter.description'),
keywords: ['integer', 'number', 'base', 'conversion', 'decimal', 'hexadecimal', 'binary', 'octal', 'base64'],
component: () => import('./integer-base-converter.vue'),
icon: ArrowsLeftRight,
diff --git a/src/tools/integer-base-converter/integer-base-converter.model.test.ts b/src/tools/integer-base-converter/integer-base-converter.model.test.ts
index d0387b64..c7d7db79 100644
--- a/src/tools/integer-base-converter/integer-base-converter.model.test.ts
+++ b/src/tools/integer-base-converter/integer-base-converter.model.test.ts
@@ -11,6 +11,9 @@ describe('integer-base-converter', () => {
expect(convertBase({ value: '10100101', fromBase: 2, toBase: 16 })).toEqual('a5');
expect(convertBase({ value: '192654', fromBase: 10, toBase: 8 })).toEqual('570216');
expect(convertBase({ value: 'zz', fromBase: 64, toBase: 10 })).toEqual('2275');
+ expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 10 })).toEqual('42540766411283223938465490632011909384');
+ expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 16 })).toEqual('20010db8000085a300000000ac1f8908');
+ expect(convertBase({ value: '20010db8000085a300000000ac1f8908', fromBase: 16, toBase: 10 })).toEqual('42540766411283223938465490632011909384');
});
});
});
diff --git a/src/tools/integer-base-converter/integer-base-converter.model.ts b/src/tools/integer-base-converter/integer-base-converter.model.ts
index b4470e57..da0fe77f 100644
--- a/src/tools/integer-base-converter/integer-base-converter.model.ts
+++ b/src/tools/integer-base-converter/integer-base-converter.model.ts
@@ -5,16 +5,16 @@ export function convertBase({ value, fromBase, toBase }: { value: string; fromBa
let decValue = value
.split('')
.reverse()
- .reduce((carry: number, digit: string, index: number) => {
+ .reduce((carry: bigint, digit: string, index: number) => {
if (!fromRange.includes(digit)) {
throw new Error(`Invalid digit "${digit}" for base ${fromBase}.`);
}
- return (carry += fromRange.indexOf(digit) * fromBase ** index);
- }, 0);
+ return (carry += BigInt(fromRange.indexOf(digit)) * BigInt(fromBase) ** BigInt(index));
+ }, 0n);
let newValue = '';
while (decValue > 0) {
- newValue = toRange[decValue % toBase] + newValue;
- decValue = (decValue - (decValue % toBase)) / toBase;
+ newValue = toRange[Number(decValue % BigInt(toBase))] + newValue;
+ decValue = (decValue - (decValue % BigInt(toBase))) / BigInt(toBase);
}
return newValue || '0';
}
diff --git a/src/tools/ipv4-address-converter/index.ts b/src/tools/ipv4-address-converter/index.ts
index 62d2daf1..66ae03a3 100644
--- a/src/tools/ipv4-address-converter/index.ts
+++ b/src/tools/ipv4-address-converter/index.ts
@@ -1,10 +1,11 @@
import { Binary } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'Ipv4 address converter',
+ name: translate('tools.ipv4-address-converter.title'),
path: '/ipv4-address-converter',
- description: 'Convert an ip address into decimal, binary, hexadecimal or event in ipv6',
+ description: translate('tools.ipv4-address-converter.description'),
keywords: ['ipv4', 'address', 'converter', 'decimal', 'hexadecimal', 'binary', 'ipv6'],
component: () => import('./ipv4-address-converter.vue'),
icon: Binary,
diff --git a/src/tools/ipv4-range-expander/index.ts b/src/tools/ipv4-range-expander/index.ts
index 233f7cc4..49dfae95 100644
--- a/src/tools/ipv4-range-expander/index.ts
+++ b/src/tools/ipv4-range-expander/index.ts
@@ -1,11 +1,11 @@
import { UnfoldMoreOutlined } from '@vicons/material';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'IPv4 range expander',
+ name: translate('tools.ipv4-range-expander.title'),
path: '/ipv4-range-expander',
- description:
- 'Given a start and an end IPv4 address this tool calculates a valid IPv4 network with its CIDR notation.',
+ description: translate('tools.ipv4-range-expander.description'),
keywords: ['ipv4', 'range', 'expander', 'subnet', 'creator', 'cidr'],
component: () => import('./ipv4-range-expander.vue'),
icon: UnfoldMoreOutlined,
diff --git a/src/tools/ipv4-subnet-calculator/index.ts b/src/tools/ipv4-subnet-calculator/index.ts
index fb4bfb43..1bae7282 100644
--- a/src/tools/ipv4-subnet-calculator/index.ts
+++ b/src/tools/ipv4-subnet-calculator/index.ts
@@ -1,10 +1,11 @@
import { RouterOutlined } from '@vicons/material';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'IPv4 subnet calculator',
+ name: translate('tools.ipv4-subnet-calculator.title'),
path: '/ipv4-subnet-calculator',
- description: 'Parse your IPv4 CIDR blocks and get all the info you need about your sub network.',
+ description: translate('tools.ipv4-subnet-calculator.description'),
keywords: ['ipv4', 'subnet', 'calculator', 'mask', 'network', 'cidr', 'netmask', 'bitmask', 'broadcast', 'address'],
component: () => import('./ipv4-subnet-calculator.vue'),
icon: RouterOutlined,
diff --git a/src/tools/ipv6-ula-generator/index.ts b/src/tools/ipv6-ula-generator/index.ts
index 24efaeba..51bfd6fc 100644
--- a/src/tools/ipv6-ula-generator/index.ts
+++ b/src/tools/ipv6-ula-generator/index.ts
@@ -1,10 +1,11 @@
import { BuildingFactory } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'IPv6 ULA generator',
+ name: translate('tools.ipv6-ula-generator.title'),
path: '/ipv6-ula-generator',
- description: 'Generate your own local, non-routable IP addresses on your network according to RFC4193.',
+ description: translate('tools.ipv6-ula-generator.description'),
keywords: ['ipv6', 'ula', 'generator', 'rfc4193', 'network', 'private'],
component: () => import('./ipv6-ula-generator.vue'),
icon: BuildingFactory,
diff --git a/src/tools/json-diff/index.ts b/src/tools/json-diff/index.ts
index 7c4c1eee..a4c0319c 100644
--- a/src/tools/json-diff/index.ts
+++ b/src/tools/json-diff/index.ts
@@ -1,10 +1,11 @@
import { CompareArrowsRound } from '@vicons/material';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'JSON diff',
+ name: translate('tools.json-diff.title'),
path: '/json-diff',
- description: 'Compare two JSON objects and get the differences between them.',
+ description: translate('tools.json-diff.description'),
keywords: ['json', 'diff', 'compare', 'difference', 'object', 'data'],
component: () => import('./json-diff.vue'),
icon: CompareArrowsRound,
diff --git a/src/tools/json-minify/index.ts b/src/tools/json-minify/index.ts
index e6a02dbe..fbe5831b 100644
--- a/src/tools/json-minify/index.ts
+++ b/src/tools/json-minify/index.ts
@@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'JSON minify',
+ name: translate('tools.json-minify.title'),
path: '/json-minify',
- description: 'Minify and compress your JSON by removing unnecessary white spaces.',
+ description: translate('tools.json-minify.description'),
keywords: ['json', 'minify', 'format'],
component: () => import('./json-minify.vue'),
icon: Braces,
diff --git a/src/tools/json-to-csv/index.ts b/src/tools/json-to-csv/index.ts
index acfef02f..9f38b82f 100644
--- a/src/tools/json-to-csv/index.ts
+++ b/src/tools/json-to-csv/index.ts
@@ -1,10 +1,11 @@
import { List } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'JSON to CSV',
+ name: translate('tools.json-to-csv.title'),
path: '/json-to-csv',
- description: 'Convert JSON to CSV with automatic header detection.',
+ description: translate('tools.json-to-csv.description'),
keywords: ['json', 'to', 'csv', 'convert'],
component: () => import('./json-to-csv.vue'),
icon: List,
diff --git a/src/tools/json-to-toml/index.ts b/src/tools/json-to-toml/index.ts
index 13e45eaf..da42c18d 100644
--- a/src/tools/json-to-toml/index.ts
+++ b/src/tools/json-to-toml/index.ts
@@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'JSON to TOML',
+ name: translate('tools.json-to-toml.title'),
path: '/json-to-toml',
- description: 'Parse and convert JSON to TOML.',
+ description: translate('tools.json-to-toml.description'),
keywords: ['json', 'parse', 'toml', 'convert', 'transform'],
component: () => import('./json-to-toml.vue'),
icon: Braces,
diff --git a/src/tools/json-to-xml/index.ts b/src/tools/json-to-xml/index.ts
new file mode 100644
index 00000000..c35ace2b
--- /dev/null
+++ b/src/tools/json-to-xml/index.ts
@@ -0,0 +1,12 @@
+import { Braces } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'JSON to XML',
+ path: '/json-to-xml',
+ description: 'Convert JSON to XML',
+ keywords: ['json', 'xml'],
+ component: () => import('./json-to-xml.vue'),
+ icon: Braces,
+ createdAt: new Date('2024-08-09'),
+});
diff --git a/src/tools/json-to-xml/json-to-xml.vue b/src/tools/json-to-xml/json-to-xml.vue
new file mode 100644
index 00000000..96a7cf16
--- /dev/null
+++ b/src/tools/json-to-xml/json-to-xml.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
diff --git a/src/tools/json-to-yaml-converter/index.ts b/src/tools/json-to-yaml-converter/index.ts
index 9db09d3e..c01e3ec0 100644
--- a/src/tools/json-to-yaml-converter/index.ts
+++ b/src/tools/json-to-yaml-converter/index.ts
@@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'JSON to YAML converter',
+ name: translate('tools.json-to-yaml-converter.title'),
path: '/json-to-yaml-converter',
- description: 'Simply convert JSON to YAML with this live online converter.',
+ description: translate('tools.json-to-yaml-converter.description'),
keywords: ['yaml', 'to', 'json'],
component: () => import('./json-to-yaml.vue'),
icon: Braces,
diff --git a/src/tools/json-viewer/index.ts b/src/tools/json-viewer/index.ts
index 6b5b8812..bc488245 100644
--- a/src/tools/json-viewer/index.ts
+++ b/src/tools/json-viewer/index.ts
@@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'JSON prettify and format',
+ name: translate('tools.json-prettify.title'),
path: '/json-prettify',
- description: 'Prettify your JSON string to a human friendly readable format.',
+ description: translate('tools.json-prettify.description'),
keywords: ['json', 'viewer', 'prettify', 'format'],
component: () => import('./json-viewer.vue'),
icon: Braces,
diff --git a/src/tools/jwt-parser/index.ts b/src/tools/jwt-parser/index.ts
index 7249ace0..939b4b34 100644
--- a/src/tools/jwt-parser/index.ts
+++ b/src/tools/jwt-parser/index.ts
@@ -1,10 +1,11 @@
import { Key } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'JWT parser',
+ name: translate('tools.jwt-parser.title'),
path: '/jwt-parser',
- description: 'Parse and decode your JSON Web Token (jwt) and display its content.',
+ description: translate('tools.jwt-parser.description'),
keywords: [
'jwt',
'parser',
diff --git a/src/tools/jwt-parser/jwt-parser.service.ts b/src/tools/jwt-parser/jwt-parser.service.ts
index cc39145a..543f4c8b 100644
--- a/src/tools/jwt-parser/jwt-parser.service.ts
+++ b/src/tools/jwt-parser/jwt-parser.service.ts
@@ -19,7 +19,7 @@ function decodeJwt({ jwt }: { jwt: string }) {
function parseClaims({ claim, value }: { claim: string; value: unknown }) {
const claimDescription = CLAIM_DESCRIPTIONS[claim];
- const formattedValue = _.isPlainObject(value) ? JSON.stringify(value, null, 3) : _.toString(value);
+ const formattedValue = _.isPlainObject(value) || _.isArray(value) ? JSON.stringify(value, null, 3) : _.toString(value);
const friendlyValue = getFriendlyValue({ claim, value });
return {
diff --git a/src/tools/jwt-parser/jwt-parser.vue b/src/tools/jwt-parser/jwt-parser.vue
index 6b30fc0c..a26064d7 100644
--- a/src/tools/jwt-parser/jwt-parser.vue
+++ b/src/tools/jwt-parser/jwt-parser.vue
@@ -39,7 +39,7 @@ const validation = useValidation({
{{ section.title }}
+
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();
+
+
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Magni reprehenderit itaque enim? Suscipit magni optio velit
quia, eveniet repellat pariatur quaerat laudantium dignissimos natus, beatae deleniti adipisci, atque necessitatibus
odio!
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit.
+ Molestias, quisquam vitae saepe dolores quas debitis ab r
+ ecusandae suscipit ex dignissimos minus quam repellat sunt.
+ Molestiae culpa blanditiis totam sapiente dignissimos.
+