+
-
-
-
- {{ $t('home.follow.p1') }}
- GitHub
- {{ $t('home.follow.p2') }}
- Twitter.
- {{ $t('home.follow.thankYou') }}
-
-
-
-
+
+
+ {{ $t('home.follow.p1') }}
+ GitHub
+ {{ $t('home.follow.p2') }}
+ Twitter.
+ {{ $t('home.follow.thankYou') }}
+
+
+
-
{{ $t('home.categories.favoriteTools') }}
-
-
-
-
-
+
+ {{ $t('home.categories.favoriteTools') }}
+
+
+
+
-
{{ t('home.categories.newestTools') }}
-
-
-
-
-
+
+ {{ t('home.categories.newestTools') }}
+
+
+
+
-
{{ $t('home.categories.allTools') }}
-
-
-
-
-
-
-
+
+ {{ $t('home.categories.allTools') }}
+
+
+
+
diff --git a/src/tools/regex-tester/index.ts b/src/tools/regex-tester/index.ts
new file mode 100644
index 00000000..62a5e234
--- /dev/null
+++ b/src/tools/regex-tester/index.ts
@@ -0,0 +1,12 @@
+import { Language } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'Regex Tester',
+ path: '/regex-tester',
+ description: 'Test your regular expressions with sample text.',
+ keywords: ['regex', 'tester', 'sample', 'expression'],
+ component: () => import('./regex-tester.vue'),
+ icon: Language,
+ createdAt: new Date('2024-09-20'),
+});
diff --git a/src/tools/regex-tester/regex-tester.service.test.ts b/src/tools/regex-tester/regex-tester.service.test.ts
new file mode 100644
index 00000000..bd4efbbc
--- /dev/null
+++ b/src/tools/regex-tester/regex-tester.service.test.ts
@@ -0,0 +1,106 @@
+import { describe, expect, it } from 'vitest';
+import { matchRegex } from './regex-tester.service';
+
+const regexesData = [
+ {
+ regex: '',
+ text: '',
+ flags: '',
+ result: [],
+ },
+ {
+ regex: '.*',
+ text: '',
+ flags: '',
+ result: [],
+ },
+ {
+ regex: '',
+ text: 'aaa',
+ flags: '',
+ result: [],
+ },
+ {
+ regex: 'a',
+ text: 'baaa',
+ flags: '',
+ result: [
+ {
+ captures: [],
+ groups: [],
+ index: 1,
+ value: 'a',
+ },
+ ],
+ },
+ {
+ regex: '(.)(?
r)',
+ text: 'azertyr',
+ flags: 'g',
+ result: [
+ {
+ captures: [
+ {
+ end: 3,
+ name: '1',
+ start: 2,
+ value: 'e',
+ },
+ {
+ end: 4,
+ name: '2',
+ start: 3,
+ value: 'r',
+ },
+ ],
+ groups: [
+ {
+ end: 4,
+ name: 'g',
+ start: 3,
+ value: 'r',
+ },
+ ],
+ index: 2,
+ value: 'er',
+ },
+ {
+ captures: [
+ {
+ end: 6,
+ name: '1',
+ start: 5,
+ value: 'y',
+ },
+ {
+ end: 7,
+ name: '2',
+ start: 6,
+ value: 'r',
+ },
+ ],
+ groups: [
+ {
+ end: 7,
+ name: 'g',
+ start: 6,
+ value: 'r',
+ },
+ ],
+ index: 5,
+ value: 'yr',
+ },
+ ],
+ },
+];
+
+describe('regex-tester', () => {
+ for (const reg of regexesData) {
+ const { regex, text, flags, result: expected_result } = reg;
+ it(`Should matchRegex("${regex}","${text}","${flags}") return correct result`, async () => {
+ const result = matchRegex(regex, text, `${flags}d`);
+
+ expect(result).to.deep.equal(expected_result);
+ });
+ }
+});
diff --git a/src/tools/regex-tester/regex-tester.service.ts b/src/tools/regex-tester/regex-tester.service.ts
new file mode 100644
index 00000000..ec8682c5
--- /dev/null
+++ b/src/tools/regex-tester/regex-tester.service.ts
@@ -0,0 +1,61 @@
+interface RegExpGroupIndices {
+ [name: string]: [number, number]
+}
+interface RegExpIndices extends Array<[number, number]> {
+ groups: RegExpGroupIndices
+}
+interface RegExpExecArrayWithIndices extends RegExpExecArray {
+ indices: RegExpIndices
+}
+interface GroupCapture {
+ name: string
+ value: string
+ start: number
+ end: number
+};
+
+export function matchRegex(regex: string, text: string, flags: string) {
+ // if (regex === '' || text === '') {
+ // return [];
+ // }
+
+ let lastIndex = -1;
+ const re = new RegExp(regex, flags);
+ const results = [];
+ let match = re.exec(text) as RegExpExecArrayWithIndices;
+ while (match !== null) {
+ if (re.lastIndex === lastIndex || match[0] === '') {
+ break;
+ }
+ const indices = match.indices;
+ const captures: Array = [];
+ Object.entries(match).forEach(([captureName, captureValue]) => {
+ if (captureName !== '0' && captureName.match(/\d+/)) {
+ captures.push({
+ name: captureName,
+ value: captureValue,
+ start: indices[Number(captureName)][0],
+ end: indices[Number(captureName)][1],
+ });
+ }
+ });
+ const groups: Array = [];
+ Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => {
+ groups.push({
+ name: groupName,
+ value: groupValue,
+ start: indices.groups[groupName][0],
+ end: indices.groups[groupName][1],
+ });
+ });
+ results.push({
+ index: match.index,
+ value: match[0],
+ captures,
+ groups,
+ });
+ lastIndex = re.lastIndex;
+ match = re.exec(text) as RegExpExecArrayWithIndices;
+ }
+ return results;
+}
diff --git a/src/tools/regex-tester/regex-tester.vue b/src/tools/regex-tester/regex-tester.vue
new file mode 100644
index 00000000..a1fa7958
--- /dev/null
+++ b/src/tools/regex-tester/regex-tester.vue
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+ See Regular Expression Cheatsheet
+
+
+
+ Global search. (g
)
+
+
+ Case-insensitive search. (i
)
+
+
+ Multiline(m
)
+
+
+ Singleline(s
)
+
+
+ Unicode(u
)
+
+
+ Unicode Sets (v
)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Index in text
+ |
+
+ Value
+ |
+
+ Captures
+ |
+
+ Groups
+ |
+
+
+
+
+ {{ match.index }} |
+ {{ match.value }} |
+
+
+ -
+ "{{ capture.name }}" = {{ capture.value }} [{{ capture.start }} - {{ capture.end }}]
+
+
+ |
+
+
+ -
+ "{{ group.name }}" = {{ group.value }} [{{ group.start }} - {{ group.end }}]
+
+
+ |
+
+
+
+
+ No match
+
+
+
+
+ {{ sample }}
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/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/ulid-generator/index.ts b/src/tools/ulid-generator/index.ts
index 6a5408dd..c12679a7 100644
--- a/src/tools/ulid-generator/index.ts
+++ b/src/tools/ulid-generator/index.ts
@@ -1,10 +1,11 @@
import { SortDescendingNumbers } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'ULID generator',
+ name: translate('tools.ulid-generator.title'),
path: '/ulid-generator',
- description: 'Generate random Universally Unique Lexicographically Sortable Identifier (ULID).',
+ description: translate('tools.ulid-generator.description'),
keywords: ['ulid', 'generator', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
component: () => import('./ulid-generator.vue'),
icon: SortDescendingNumbers,
diff --git a/src/tools/url-encoder/index.ts b/src/tools/url-encoder/index.ts
index bd19b890..ab85118c 100644
--- a/src/tools/url-encoder/index.ts
+++ b/src/tools/url-encoder/index.ts
@@ -1,10 +1,11 @@
import { Link } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'Encode/decode url formatted strings',
+ name: translate('tools.url-encoder.title'),
path: '/url-encoder',
- description: 'Encode to url-encoded format (also known as "percent-encoded") or decode from it.',
+ description: translate('tools.url-encoder.description'),
keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'],
component: () => import('./url-encoder.vue'),
icon: Link,
diff --git a/src/tools/url-encoder/url-encoder.vue b/src/tools/url-encoder/url-encoder.vue
index c43f8193..19025190 100644
--- a/src/tools/url-encoder/url-encoder.vue
+++ b/src/tools/url-encoder/url-encoder.vue
@@ -23,7 +23,7 @@ const decodeInput = ref('Hello%20world%20%3A)');
const decodeOutput = computed(() => withDefaultOnError(() => decodeURIComponent(decodeInput.value), ''));
const decodeValidation = useValidation({
- source: encodeInput,
+ source: decodeInput,
rules: [
{
validator: value => isNotThrowing(() => decodeURIComponent(value)),
diff --git a/src/tools/url-parser/index.ts b/src/tools/url-parser/index.ts
index d1c8dfe8..77976a24 100644
--- a/src/tools/url-parser/index.ts
+++ b/src/tools/url-parser/index.ts
@@ -1,11 +1,11 @@
import { Unlink } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'Url parser',
+ name: translate('tools.url-parser.title'),
path: '/url-parser',
- description:
- 'Parse an url string to get all the different parts (protocol, origin, params, port, username-password, ...)',
+ description: translate('tools.url-parser.description'),
keywords: ['url', 'parser', 'protocol', 'origin', 'params', 'port', 'username', 'password', 'href'],
component: () => import('./url-parser.vue'),
icon: Unlink,
diff --git a/src/tools/user-agent-parser/index.ts b/src/tools/user-agent-parser/index.ts
index 1ae05d14..4d026145 100644
--- a/src/tools/user-agent-parser/index.ts
+++ b/src/tools/user-agent-parser/index.ts
@@ -1,10 +1,11 @@
import { Browser } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'User-agent parser',
+ name: translate('tools.user-agent-parser.title'),
path: '/user-agent-parser',
- description: 'Detect and parse Browser, Engine, OS, CPU, and Device type/model from an user-agent string.',
+ description: translate('tools.user-agent-parser.description'),
keywords: ['user', 'agent', 'parser', 'browser', 'engine', 'os', 'cpu', 'device', 'user-agent', 'client'],
component: () => import('./user-agent-parser.vue'),
icon: Browser,
diff --git a/src/tools/uuid-generator/index.ts b/src/tools/uuid-generator/index.ts
index 9289b902..54ec479f 100644
--- a/src/tools/uuid-generator/index.ts
+++ b/src/tools/uuid-generator/index.ts
@@ -1,11 +1,11 @@
import { Fingerprint } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'UUIDs generator',
+ name: translate('tools.uuid-generator.title'),
path: '/uuid-generator',
- description:
- 'A Universally Unique Identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot!).',
+ description: translate('tools.uuid-generator.description'),
keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique', 'v1', 'v3', 'v5', 'nil'],
component: () => import('./uuid-generator.vue'),
icon: Fingerprint,
diff --git a/src/tools/wifi-qr-code-generator/index.ts b/src/tools/wifi-qr-code-generator/index.ts
index ad0135c3..b59b95df 100644
--- a/src/tools/wifi-qr-code-generator/index.ts
+++ b/src/tools/wifi-qr-code-generator/index.ts
@@ -1,11 +1,11 @@
import { Qrcode } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'WiFi QR Code generator',
+ name: translate('tools.wifi-qrcode-generator.title'),
path: '/wifi-qrcode-generator',
- description:
- 'Generate and download QR-codes for quick connections to WiFi networks.',
+ description: translate('tools.wifi-qrcode-generator.description'),
keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent', 'wifi'],
component: () => import('./wifi-qr-code-generator.vue'),
icon: Qrcode,
diff --git a/src/tools/xml-formatter/index.ts b/src/tools/xml-formatter/index.ts
index fe28d3ae..7aa096da 100644
--- a/src/tools/xml-formatter/index.ts
+++ b/src/tools/xml-formatter/index.ts
@@ -1,10 +1,11 @@
import { Code } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'XML formatter',
+ name: translate('tools.xml-formatter.title'),
path: '/xml-formatter',
- description: 'Prettify your XML string to a human friendly readable format.',
+ description: translate('tools.xml-formatter.description'),
keywords: ['xml', 'prettify', 'format'],
component: () => import('./xml-formatter.vue'),
icon: Code,
diff --git a/src/tools/xml-to-json/index.ts b/src/tools/xml-to-json/index.ts
new file mode 100644
index 00000000..8d83f4fe
--- /dev/null
+++ b/src/tools/xml-to-json/index.ts
@@ -0,0 +1,12 @@
+import { Braces } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'XML to JSON',
+ path: '/xml-to-json',
+ description: 'Convert XML to JSON',
+ keywords: ['xml', 'json'],
+ component: () => import('./xml-to-json.vue'),
+ icon: Braces,
+ createdAt: new Date('2024-08-09'),
+});
diff --git a/src/tools/xml-to-json/xml-to-json.vue b/src/tools/xml-to-json/xml-to-json.vue
new file mode 100644
index 00000000..e1e5a477
--- /dev/null
+++ b/src/tools/xml-to-json/xml-to-json.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
diff --git a/src/tools/yaml-to-json-converter/index.ts b/src/tools/yaml-to-json-converter/index.ts
index 724ecdb7..60110f09 100644
--- a/src/tools/yaml-to-json-converter/index.ts
+++ b/src/tools/yaml-to-json-converter/index.ts
@@ -1,10 +1,11 @@
import { AlignJustified } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'YAML to JSON converter',
+ name: translate('tools.yaml-to-json-converter.title'),
path: '/yaml-to-json-converter',
- description: 'Simply convert YAML to JSON with this live online converter.',
+ description: translate('tools.yaml-to-json-converter.description'),
keywords: ['yaml', 'to', 'json'],
component: () => import('./yaml-to-json.vue'),
icon: AlignJustified,
diff --git a/src/tools/yaml-to-toml/index.ts b/src/tools/yaml-to-toml/index.ts
index b6a7077f..d788887e 100644
--- a/src/tools/yaml-to-toml/index.ts
+++ b/src/tools/yaml-to-toml/index.ts
@@ -1,10 +1,11 @@
import { AlignJustified } from '@vicons/tabler';
import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
- name: 'YAML to TOML',
+ name: translate('tools.yaml-to-toml.title'),
path: '/yaml-to-toml',
- description: 'Parse and convert YAML to TOML.',
+ description: translate('tools.yaml-to-toml.description'),
keywords: ['yaml', 'to', 'toml', 'convert', 'transform'],
component: () => import('./yaml-to-toml.vue'),
icon: AlignJustified,
diff --git a/src/tools/yaml-viewer/index.ts b/src/tools/yaml-viewer/index.ts
new file mode 100644
index 00000000..f3043270
--- /dev/null
+++ b/src/tools/yaml-viewer/index.ts
@@ -0,0 +1,13 @@
+import { AlignJustified } from '@vicons/tabler';
+import { defineTool } from '../tool';
+import { translate } from '@/plugins/i18n.plugin';
+
+export const tool = defineTool({
+ name: translate('tools.yaml-prettify.title'),
+ path: '/yaml-prettify',
+ description: translate('tools.yaml-prettify.description'),
+ keywords: ['yaml', 'viewer', 'prettify', 'format'],
+ component: () => import('./yaml-viewer.vue'),
+ icon: AlignJustified,
+ createdAt: new Date('2024-01-31'),
+});
diff --git a/src/tools/yaml-viewer/yaml-models.ts b/src/tools/yaml-viewer/yaml-models.ts
new file mode 100644
index 00000000..54569db8
--- /dev/null
+++ b/src/tools/yaml-viewer/yaml-models.ts
@@ -0,0 +1,24 @@
+import { type MaybeRef, get } from '@vueuse/core';
+
+import yaml from 'yaml';
+
+export { formatYaml };
+
+function formatYaml({
+ rawYaml,
+ sortKeys = false,
+ indentSize = 2,
+}: {
+ rawYaml: MaybeRef
+ sortKeys?: MaybeRef
+ indentSize?: MaybeRef
+}) {
+ const parsedYaml = yaml.parse(get(rawYaml));
+
+ const formattedYAML = yaml.stringify(parsedYaml, {
+ sortMapEntries: get(sortKeys),
+ indent: get(indentSize),
+ });
+
+ return formattedYAML;
+}
diff --git a/src/tools/yaml-viewer/yaml-viewer.vue b/src/tools/yaml-viewer/yaml-viewer.vue
new file mode 100644
index 00000000..3385eee2
--- /dev/null
+++ b/src/tools/yaml-viewer/yaml-viewer.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/utils/base64.test.ts b/src/utils/base64.test.ts
index 994f1b1b..51d15239 100644
--- a/src/utils/base64.test.ts
+++ b/src/utils/base64.test.ts
@@ -38,7 +38,8 @@ describe('base64 utils', () => {
it('should throw for incorrect base64 string', () => {
expect(() => base64ToText('a')).to.throw('Incorrect base64 string');
- expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
+ // should not really be false because trimming of space is now implied
+ // expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
expect(() => base64ToText('Ă©')).to.throw('Incorrect base64 string');
// missing final '='
expect(() => base64ToText('bG9yZW0gaXBzdW0')).to.throw('Incorrect base64 string');
@@ -56,17 +57,17 @@ describe('base64 utils', () => {
it('should return false for incorrect base64 string', () => {
expect(isValidBase64('a')).to.eql(false);
- expect(isValidBase64(' ')).to.eql(false);
expect(isValidBase64('Ă©')).to.eql(false);
expect(isValidBase64('data:text/plain;notbase64,YQ==')).to.eql(false);
// missing final '='
expect(isValidBase64('bG9yZW0gaXBzdW0')).to.eql(false);
});
- it('should return false for untrimmed correct base64 string', () => {
- expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(false);
- expect(isValidBase64(' LTE=')).to.eql(false);
- expect(isValidBase64(' YQ== ')).to.eql(false);
+ it('should return true for untrimmed correct base64 string', () => {
+ expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(true);
+ expect(isValidBase64(' LTE=')).to.eql(true);
+ expect(isValidBase64(' YQ== ')).to.eql(true);
+ expect(isValidBase64(' ')).to.eql(true);
});
});
diff --git a/src/utils/base64.ts b/src/utils/base64.ts
index 16912ee3..44e59f41 100644
--- a/src/utils/base64.ts
+++ b/src/utils/base64.ts
@@ -1,7 +1,9 @@
+import { Base64 } from 'js-base64';
+
export { textToBase64, base64ToText, isValidBase64, removePotentialDataAndMimePrefix };
function textToBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
- const encoded = window.btoa(str);
+ const encoded = Base64.encode(str);
return makeUrlSafe ? makeUriSafe(encoded) : encoded;
}
@@ -16,7 +18,7 @@ function base64ToText(str: string, { makeUrlSafe = false }: { makeUrlSafe?: bool
}
try {
- return window.atob(cleanStr);
+ return Base64.decode(cleanStr);
}
catch (_) {
throw new Error('Incorrect base64 string');
@@ -34,10 +36,11 @@ function isValidBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boo
}
try {
+ const reEncodedBase64 = Base64.fromUint8Array(Base64.toUint8Array(cleanStr));
if (makeUrlSafe) {
- return removePotentialPadding(window.btoa(window.atob(cleanStr))) === cleanStr;
+ return removePotentialPadding(reEncodedBase64) === cleanStr;
}
- return window.btoa(window.atob(cleanStr)) === cleanStr;
+ return reEncodedBase64 === cleanStr.replace(/\s/g, '');
}
catch (err) {
return false;
diff --git a/vite.config.ts b/vite.config.ts
index 00f90c33..42a2cb29 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,20 +1,20 @@
-import { URL, fileURLToPath } from 'node:url';
import { resolve } from 'node:path';
+import { URL, fileURLToPath } from 'node:url';
-import { defineConfig } from 'vite';
+import VueI18n from '@intlify/unplugin-vue-i18n/vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
+import Unocss from 'unocss/vite';
+import AutoImport from 'unplugin-auto-import/vite';
+import IconsResolver from 'unplugin-icons/resolver';
+import Icons from 'unplugin-icons/vite';
+import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
+import Components from 'unplugin-vue-components/vite';
+import { defineConfig } from 'vite';
+import { VitePWA } from 'vite-plugin-pwa';
import markdown from 'vite-plugin-vue-markdown';
import svgLoader from 'vite-svg-loader';
-import { VitePWA } from 'vite-plugin-pwa';
-import AutoImport from 'unplugin-auto-import/vite';
-import Components from 'unplugin-vue-components/vite';
-import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
-import Unocss from 'unocss/vite';
import { configDefaults } from 'vitest/config';
-import Icons from 'unplugin-icons/vite';
-import IconsResolver from 'unplugin-icons/resolver';
-import VueI18n from '@intlify/unplugin-vue-i18n/vite';
const baseUrl = process.env.BASE_URL ?? '/';
@@ -23,9 +23,13 @@ export default defineConfig({
plugins: [
VueI18n({
runtimeOnly: true,
+ jitCompilation: true,
compositionOnly: true,
fullInstall: true,
- include: [resolve(__dirname, 'locales/**')],
+ strictMessage: false,
+ include: [
+ resolve(__dirname, 'locales/**'),
+ ],
}),
AutoImport({
imports: [