This commit is contained in:
steffenrapp 2025-04-08 11:42:50 +02:00 committed by GitHub
commit 0d85581ef3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 368 additions and 102 deletions

View file

@ -1,16 +1,9 @@
'404':
notFound: 404 Nicht gefunden
sorry: Entschuldigung, diese Seite scheint nicht zu existieren
maybe: >-
Vielleicht macht der Cache etwas Seltsames. Mit einem erzwungenen Neuladen
versuchen?
backHome: Zurück zur Startseite
home: home:
categories: categories:
newestTools: Neueste Tools newestTools: Neueste Tools
favoriteTools: Deine Lieblingstools favoriteTools: Deine Lieblingstools
allTools: Alle Tools allTools: Alle Tools
favoritesDndToolTip: 'Ziehen und Ablegen, um Favoriten neu zu ordnen' favoritesDndToolTip: Drag and Drop, um Favoriten neu zu ordnen
subtitle: Praktische Tools für Entwickler subtitle: Praktische Tools für Entwickler
toggleMenu: Menü umschalten toggleMenu: Menü umschalten
home: Startseite home: Startseite
@ -72,6 +65,13 @@ about:
funktioniert, melde bitte einen Fehler im funktioniert, melde bitte einen Fehler im
[Issues-Bereich](https://github.com/CorentinTh/it-tools/issues/new/choose) [Issues-Bereich](https://github.com/CorentinTh/it-tools/issues/new/choose)
im GitHub-Repository. im GitHub-Repository.
404:
notFound: 404 Nicht gefunden
sorry: Entschuldigung, diese Seite scheint nicht zu existieren
maybe: >-
Vielleicht macht der Cache etwas Seltsames. Mit einem erzwungenen Neuladen
versuchen?
backHome: Zurück zur Startseite
favoriteButton: favoriteButton:
remove: Aus Favoriten entfernen remove: Aus Favoriten entfernen
add: Zu Favoriten hinzufügen add: Zu Favoriten hinzufügen
@ -79,6 +79,20 @@ toolCard:
new: Neu new: Neu
search: search:
label: Suche label: Suche
placeholder: Tippe, um ein Tool oder einen Befehl zu suchen...
textareaCopyable:
copy: In die Zwischenablage kopieren
copied: Kopiert!
spanCopyable:
copy: In die Zwischenablage kopieren
copied: Kopiert!
inputCopyable:
copy: In die Zwischenablage kopieren
copied: Kopiert!
formatTransformer:
input: Eingabe
input-placeholder: Eingabe...
output: Ausgabe
tools: tools:
categories: categories:
favorite-tools: Deine Lieblingstools favorite-tools: Deine Lieblingstools
@ -102,6 +116,10 @@ tools:
description: >- description: >-
Überwache die Dauer einer Sache. Im Grunde ein Chronometer mit einfachen Überwache die Dauer einer Sache. Im Grunde ein Chronometer mit einfachen
Chronometerfunktionen. Chronometerfunktionen.
button:
start: Start
stop: Stopp
reset: Zurücksetzen
token-generator: token-generator:
title: Token-Generator title: Token-Generator
description: >- description: >-
@ -296,6 +314,13 @@ tools:
description: >- description: >-
Generiere und downloade QR-Codes für eine URL oder einfach einen Text und Generiere und downloade QR-Codes für eine URL oder einfach einen Text und
passe die Hintergrund- und Vordergrundfarben an. passe die Hintergrund- und Vordergrundfarben an.
text: 'Text:'
placeholder: Dein Link oder Text...
foreground-color: 'Vordergrundfarbe:'
background-color: 'Hintergrundfarbe:'
error-resistance: 'Fehlerresistenz:'
button:
download: QR-Code herunterladen
wifi-qrcode-generator: wifi-qrcode-generator:
title: WLAN-QR-Code-Generator title: WLAN-QR-Code-Generator
description: >- description: >-
@ -420,6 +445,11 @@ tools:
description: >- description: >-
Informationen zu einem Text erhalten, wie die Anzahl der Zeichen, die Informationen zu einem Text erhalten, wie die Anzahl der Zeichen, die
Anzahl der Wörter, die Größe usw. Anzahl der Wörter, die Größe usw.
characters: Anzahl Zeichen
words: Anzahl Wörter
lines: Anzahl Zeilen
bytes: Bytegröße
placeholder: Dein Text...
text-to-nato-alphabet: text-to-nato-alphabet:
title: Text zu NATO-Alphabet title: Text zu NATO-Alphabet
description: >- description: >-
@ -430,6 +460,13 @@ tools:
description: >- description: >-
Generiere einen Base64-Basic-Auth-Header aus einem Benutzernamen und einem Generiere einen Base64-Basic-Auth-Header aus einem Benutzernamen und einem
Passwort. Passwort.
button:
copy: Header kopieren
copied: Header in die Zwischenablage kopiert
password: Passwort
username: Benutzername
yourpassword: Dein Passwort...
yourusername: Dein Benutzername...
text-to-unicode: text-to-unicode:
title: Text zu Unicode title: Text zu Unicode
description: Parse und konvertiere Text in Unicode und umgekehrt. description: Parse und konvertiere Text in Unicode und umgekehrt.
@ -454,3 +491,80 @@ tools:
text-to-binary: text-to-binary:
title: Text zu ASCII-Binär title: Text zu ASCII-Binär
description: Konvertiere Text in seine ASCII-Binärrepräsentation und umgekehrt. description: Konvertiere Text in seine ASCII-Binärrepräsentation und umgekehrt.
safelink-decoder:
title: Outlook Safelink-Decoder
description: Outlook Safelinks decodieren
input: 'Eingabe einer Outlook Safelink-URL:'
input-placeholder: Deine eingegebene Outlook Safelink-URL...
output: 'Ausgabe der decodierten URL:'
ascii-text-drawer:
title: ASCII-Art-Text-Generator
description: ASCII-Art-Text mit vielen Schriftarten und Stilen erstellen.
text: 'Dein Text:'
placeholder: Dein zu zeichnender Text
font: 'Schriftart:'
width: 'Breite:'
loading: Schriftart wird geladen...
error: Die aktuellen Einstellungen führten zu einem Fehler.
output: 'ASCII-Art-Text:'
json-to-xml:
title: JSON zu XML
description: JSON in XML konvertieren
input: Dein JSON-Inhalt
input-placeholder: Füge hier deinen JSON-Inhalt ein...
output: Konvertiertes XML
error: Bereitgestelltes JSON ist ungültig.
xml-to-json:
title: XML zu JSON
description: XML in JSON konvertieren
input: Dein XML-Inhalt
input-placeholder: Füge hier deinen XML-Inhalt ein...
output: Konvertiertes JSON
error: Bereitgestelltes XML ist ungültig.
email-normalizer:
title: E-Mail-Normalisierung
description: >-
Vereinheitlichen von E-Mail-Adressen auf ein Standardformat für einen
einfacheren Vergleich. Nützlich für Deduplizierung und Datenbereinigung.
input: 'Unbearbeitete E-Mails zur Normalisierung:'
input-placeholder: Gib hier deine E-Mails ein (eine pro Zeile)...
output: 'Normalisierte E-Mails:'
output-placeholder: Hier werden normalisierte E-Mails angezeigt...
button:
clear: E-Mails leeren
copy: Kopiere normalisierte E-Mails
copied: Normalisierte E-Mails in die Zwischenablage kopiert
markdown-to-html:
title: Markdown zu HTML
description: Markdown in HTML konvertieren und (als PDF) ausdrucken
markdown: 'Dein zu konvertierender Markdown-Inhalt:'
markdownInput: Dein Markdown-Inhalt...
html: 'HTML-Ausgabe:'
button:
print: Als PDF drucken
regex-memo:
title: Regex-Spickzettel
description: Spickzettel für Javascript Regex/Regulärer Ausdruck
regex-tester:
title: Regex-Tester
description: Teste deine regulären Ausdrücke mit Beispieltext.
regex: Regex
regex-input: 'Regex zum Testen:'
regex-input-placeholder: Eingabe des zu testenden regulären Ausdrucks
link: Siehe Spickzettel für reguläre Ausdrücke
text-input: 'Zu prüfender Text:'
text-input-placeholder: Eingabe des zu prüfenden Texts
matches: Treffer
text-index: Index im Text
value: Wert
captures: Erfassungen
groups: Gruppen
sample: Beispiel für passenden Text
diagram: Regex-Diagramm
global: Globale Suche.
ignoreCase: Suche ohne Berücksichtigung der Groß-/Kleinschreibung.
multiline: Ermöglicht die Übereinstimmung von ^ und $ neben Zeilenumbruchzeichen.
dotAll: Lässt . als Treffer für Zeilenumbruchzeichen zu.
unicode: Unicode; behandelt ein Muster als eine Folge von Unicode-Codepunkten.
unicodeSets: Ein Upgrade zum u-Modus mit mehr Unicode-Funktionen.
no-match: Kein Treffer

View file

@ -57,6 +57,20 @@ toolCard:
new: New new: New
search: search:
label: Search label: Search
placeholder: Type to search a tool or a command...
textareaCopyable:
copy: Copy to clipboard
copied: Copied!
spanCopyable:
copy: Copy to clipboard
copied: Copied!
inputCopyable:
copy: Copy to clipboard
copied: Copied!
formatTransformer:
input: Input
input-placeholder: Input...
output: Output
tools: tools:
categories: categories:
favorite-tools: 'Your favorite tools' favorite-tools: 'Your favorite tools'
@ -78,7 +92,11 @@ tools:
chronometer: chronometer:
title: Chronometer title: Chronometer
description: Monitor the duration of a thing. Basically a chronometer with simple chronometer features. description: Monitor the duration of a thing. Basically a chronometer with simple chronometer features.
button:
start: Start
stop: Stop
reset: Reset
token-generator: token-generator:
title: Token generator title: Token generator
description: Generate random string with the chars you want, uppercase or lowercase letters, numbers and/or symbols. description: Generate random string with the chars you want, uppercase or lowercase letters, numbers and/or symbols.
@ -252,6 +270,13 @@ tools:
qrcode-generator: qrcode-generator:
title: QR Code generator title: QR Code generator
description: Generate and download a QR code for a URL (or just plain text), and customize the background and foreground colors. description: Generate and download a QR code for a URL (or just plain text), and customize the background and foreground colors.
error-resistance: 'Error resistance:'
background-color: 'Background color:'
foreground-color: 'Foreground color:'
text: 'Text:'
placeholder: Your link or text...
button:
download: Download QR code
wifi-qrcode-generator: wifi-qrcode-generator:
title: WiFi QR Code generator title: WiFi QR Code generator
@ -360,6 +385,11 @@ tools:
text-statistics: text-statistics:
title: Text statistics title: Text statistics
description: Get information about a text, the number of characters, the number of words, its size in bytes, ... description: Get information about a text, the number of characters, the number of words, its size in bytes, ...
characters: Character count
words: Word count
lines: Line count
bytes: Byte size
placeholder: Your text...
text-to-nato-alphabet: text-to-nato-alphabet:
title: Text to NATO alphabet title: Text to NATO alphabet
@ -368,6 +398,13 @@ tools:
basic-auth-generator: basic-auth-generator:
title: Basic auth generator title: Basic auth generator
description: Generate a base64 basic auth header from a username and password. description: Generate a base64 basic auth header from a username and password.
button:
copy: Copy header
username: Username
yourusername: Your username...
password: Password
yourpassword: Your password...
copied: Header copied to the clipboard
text-to-unicode: text-to-unicode:
title: Text to Unicode title: Text to Unicode
@ -392,3 +429,88 @@ tools:
text-to-binary: text-to-binary:
title: Text to ASCII binary title: Text to ASCII binary
description: Convert text to its ASCII binary representation and vice-versa. description: Convert text to its ASCII binary representation and vice-versa.
safelink-decoder:
title: Outlook Safelink decoder
description: Decode Outlook SafeLink links
input: 'Your input Outlook SafeLink Url:'
input-placeholder: Your input Outlook SafeLink Url...
output: 'Output decoded URL:'
ascii-text-drawer:
title: ASCII Art Text Generator
description: Create ASCII art text with many fonts and styles.
text: 'Your text:'
placeholder: Your text to draw
output: 'Ascii Art text:'
font: 'Font:'
width: 'Width:'
loading: Loading font...
error: Current settings resulted in error.
json-to-xml:
title: JSON to XML
description: Convert JSON to XML
input: Your JSON content
input-placeholder: Paste your JSON content here...
output: Converted XML
error: Provided JSON is not valid.
xml-to-json:
title: XML to JSON
description: Convert XML to JSON
input: Your XML content
input-placeholder: Paste your XML content here...
output: Converted JSON
error: Provided XML is not valid.
email-normalizer:
title: Email normalizer
description: >-
Normalize email addresses to a standard format for easier comparison.
Useful for deduplication and data cleaning.
input: 'Raw emails to normalize:'
output: 'Normalized emails:'
input-placeholder: Put your emails here (one per line)...
output-placeholder: Normalized emails will appear here...
button:
clear: Clear emails
copy: Copy normalized emails
copied: Normalized emails copied to the clipboard
markdown-to-html:
title: Markdown to HTML
description: Convert Markdown to HTML and allow to print (as PDF)
markdown: 'Your Markdown to convert:'
markdownInput: Your Markdown content...
html: 'Output HTML:'
button:
print: Print as PDF
regex-memo:
title: Regex cheatsheet
description: Javascript Regex/Regular Expression cheatsheet
regex-tester:
title: Regex Tester
description: Test your regular expressions with sample text.
regex-input: 'Regex to test:'
regex-input-placeholder: Put the regex to test
link: See Regular Expression Cheatsheet
text-input: 'Text to match:'
text-input-placeholder: Put the text to match
matches: Matches
text-index: Index in text
value: Value
captures: Captures
groups: Groups
sample: Sample matching text
diagram: Regex Diagram
global: Global search
ignoreCase: Case-insensitive search
multiline: Allows ^ and $ to match next to newline characters.
dotAll: Allows . to match newline characters.
unicode: Unicode; treat a pattern as a sequence of Unicode code points.
unicodeSets: An upgrade to the u mode with more Unicode features.
regex: Regex
no-match: No match

View file

@ -2,6 +2,7 @@
import _ from 'lodash'; import _ from 'lodash';
import type { UseValidationRule } from '@/composable/validation'; import type { UseValidationRule } from '@/composable/validation';
import CInputText from '@/ui/c-input-text/c-input-text.vue'; import CInputText from '@/ui/c-input-text/c-input-text.vue';
import { translate as t } from '@/plugins/i18n.plugin';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -16,10 +17,10 @@ const props = withDefaults(
{ {
transformer: _.identity, transformer: _.identity,
inputValidationRules: () => [], inputValidationRules: () => [],
inputLabel: 'Input', inputLabel: t('formatTransformer.input'),
inputDefault: '', inputDefault: '',
inputPlaceholder: 'Input...', inputPlaceholder: t('formatTransformer.input-placeholder'),
outputLabel: 'Output', outputLabel: t('formatTransformer.output'),
outputLanguage: '', outputLanguage: '',
}, },
); );

View file

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { useCopy } from '@/composable/copy'; import { useCopy } from '@/composable/copy';
import { translate as t } from '@/plugins/i18n.plugin';
const props = defineProps<{ value: string }>(); const props = defineProps<{ value: string }>();
const emit = defineEmits(['update:value']); const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit); const value = useVModel(props, 'value', emit);
const { copy, isJustCopied } = useCopy({ source: value, createToast: false }); const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : 'Copy to clipboard'); const tooltipText = computed(() => isJustCopied.value ? t('inputCopyable.copied') : t('inputCopyable.copy'));
</script> </script>
<template> <template>

View file

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useCopy } from '@/composable/copy'; import { useCopy } from '@/composable/copy';
import { translate as t } from '@/plugins/i18n.plugin';
const props = withDefaults(defineProps<{ value?: string }>(), { value: '' }); const props = withDefaults(defineProps<{ value?: string }>(), { value: '' });
const { value } = toRefs(props); const { value } = toRefs(props);
const initialText = 'Copy to clipboard'; const initialText = t('spanCopyable.copy');
const { copy, isJustCopied } = useCopy({ source: value, createToast: false }); const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : initialText); const tooltipText = computed(() => isJustCopied.value ? t('spanCopyable.copied') : initialText);
</script> </script>
<template> <template>

View file

@ -9,6 +9,7 @@ import yamlHljs from 'highlight.js/lib/languages/yaml';
import iniHljs from 'highlight.js/lib/languages/ini'; import iniHljs from 'highlight.js/lib/languages/ini';
import markdownHljs from 'highlight.js/lib/languages/markdown'; import markdownHljs from 'highlight.js/lib/languages/markdown';
import { useCopy } from '@/composable/copy'; import { useCopy } from '@/composable/copy';
import { translate as t } from '@/plugins/i18n.plugin';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -22,7 +23,7 @@ const props = withDefaults(
followHeightOf: null, followHeightOf: null,
language: 'txt', language: 'txt',
copyPlacement: 'top-right', copyPlacement: 'top-right',
copyMessage: 'Copy to clipboard', copyMessage: t('textareaCopyable.copy'),
}, },
); );
hljs.registerLanguage('sql', sqlHljs); hljs.registerLanguage('sql', sqlHljs);
@ -37,7 +38,7 @@ const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(p
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) }; const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
const { copy, isJustCopied } = useCopy({ source: value, createToast: false }); const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value); const tooltipText = computed(() => isJustCopied.value ? t('textareaCopyable.copied') : copyMessage.value);
</script> </script>
<template> <template>

View file

@ -125,7 +125,7 @@ function activateOption(option: PaletteOption) {
</c-button> </c-button>
<c-modal v-model:open="isModalOpen" class="palette-modal" shadow-xl important:max-w-650px important:pa-12px @keydown="handleKeydown"> <c-modal v-model:open="isModalOpen" class="palette-modal" shadow-xl important:max-w-650px important:pa-12px @keydown="handleKeydown">
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable /> <c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text :placeholder="$t('search.placeholder')" autofocus clearable />
<div v-for="(options, category) in filteredSearchResult" :key="category"> <div v-for="(options, category) in filteredSearchResult" :key="category">
<div ml-3 mt-3 text-sm font-bold text-primary op-60> <div ml-3 mt-3 text-sm font-bold text-primary op-60>

View file

@ -8,6 +8,7 @@ const width = useStorage('ascii-text-drawer:width', 80);
const output = ref(''); const output = ref('');
const errored = ref(false); const errored = ref(false);
const processing = ref(false); const processing = ref(false);
const { t } = useI18n();
figlet.defaults({ fontPath: '//unpkg.com/figlet@1.6.0/fonts/' }); figlet.defaults({ fontPath: '//unpkg.com/figlet@1.6.0/fonts/' });
@ -44,8 +45,8 @@ const fonts = ['1Row', '3-D', '3D Diagonal', '3D-ASCII', '3x5', '4Max', '5 Line
<c-card style="max-width: 600px;"> <c-card style="max-width: 600px;">
<c-input-text <c-input-text
v-model:value="input" v-model:value="input"
label="Your text:" :label="t('tools.ascii-text-drawer.text')"
placeholder="Your text to draw" :placeholder="t('tools.ascii-text-drawer.placeholder')"
raw-text raw-text
multiline multiline
rows="4" rows="4"
@ -58,14 +59,14 @@ const fonts = ['1Row', '3-D', '3D Diagonal', '3D-ASCII', '3x5', '4Max', '5 Line
<c-select <c-select
v-model:value="font" v-model:value="font"
label-position="top" label-position="top"
label="Font:" :label="t('tools.ascii-text-drawer.font')"
:options="fonts" :options="fonts"
searchable="true" searchable="true"
placeholder="Select font to use" placeholder="Select font to use"
/> />
</n-gi> </n-gi>
<n-gi span="2"> <n-gi span="2">
<n-form-item label="Width:" label-placement="top" label-width="100" :show-feedback="false"> <n-form-item :label="t('tools.ascii-text-drawer.width')" label-placement="top" label-width="100" :show-feedback="false">
<n-input-number v-model:value="width" min="0" max="10000" w-full placeholder="Width of the text" /> <n-input-number v-model:value="width" min="0" max="10000" w-full placeholder="Width of the text" />
</n-form-item> </n-form-item>
</n-gi> </n-gi>
@ -75,14 +76,14 @@ const fonts = ['1Row', '3-D', '3D Diagonal', '3D-ASCII', '3x5', '4Max', '5 Line
<div v-if="processing" flex items-center justify-center> <div v-if="processing" flex items-center justify-center>
<n-spin size="medium" /> <n-spin size="medium" />
<span class="ml-2">Loading font...</span> <span class="ml-2">{{ t('tools.ascii-text-drawer.loading') }}</span>
</div> </div>
<c-alert v-if="errored" mt-1 text-center type="error"> <c-alert v-if="errored" mt-1 text-center type="error">
Current settings resulted in error. {{ t('tools.ascii-text-drawer.error') }}
</c-alert> </c-alert>
<n-form-item v-if="!processing && !errored" label="Ascii Art text:"> <n-form-item v-if="!processing && !errored" :label="t('tools.ascii-text-drawer.output')">
<TextareaCopyable <TextareaCopyable
:value="output" :value="output"
mb-1 mt-1 mb-1 mt-1

View file

@ -1,10 +1,11 @@
import { Artboard } from '@vicons/tabler'; import { Artboard } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'ASCII Art Text Generator', name: translate('tools.ascii-text-drawer.title'),
path: '/ascii-text-drawer', path: '/ascii-text-drawer',
description: 'Create ASCII art text with many fonts and styles.', description: translate('tools.ascii-text-drawer.description'),
keywords: ['ascii', 'asciiart', 'text', 'drawer'], keywords: ['ascii', 'asciiart', 'text', 'drawer'],
component: () => import('./ascii-text-drawer.vue'), component: () => import('./ascii-text-drawer.vue'),
icon: Artboard, icon: Artboard,

View file

@ -5,17 +5,25 @@ import { textToBase64 } from '@/utils/base64';
const username = ref(''); const username = ref('');
const password = ref(''); const password = ref('');
const header = computed(() => `Authorization: Basic ${textToBase64(`${username.value}:${password.value}`)}`); const header = computed(() => `Authorization: Basic ${textToBase64(`${username.value}:${password.value}`)}`);
const { t } = useI18n();
const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard' }); const { copy } = useCopy({ source: header, text: t('tools.basic-auth-generator.copied') });
</script> </script>
<template> <template>
<div> <div>
<c-input-text v-model:value="username" label="Username" placeholder="Your username..." clearable raw-text mb-5 /> <c-input-text
v-model:value="username"
:label="t('tools.basic-auth-generator.username')"
:placeholder="t('tools.basic-auth-generator.yourusername')"
clearable
raw-text
mb-5
/>
<c-input-text <c-input-text
v-model:value="password" v-model:value="password"
label="Password" :label="t('tools.basic-auth-generator.password')"
placeholder="Your password..." :placeholder="t('tools.basic-auth-generator.yourpassword')"
clearable clearable
raw-text raw-text
mb-2 mb-2
@ -31,7 +39,7 @@ const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard
</c-card> </c-card>
<div mt-5 flex justify-center> <div mt-5 flex justify-center>
<c-button @click="copy()"> <c-button @click="copy()">
Copy header {{ t('tools.basic-auth-generator.button.copy') }}
</c-button> </c-button>
</div> </div>
</div> </div>

View file

@ -5,6 +5,7 @@ import { formatMs } from './chronometer.service';
const isRunning = ref(false); const isRunning = ref(false);
const counter = ref(0); const counter = ref(0);
const { t } = useI18n();
let previousRafDate = Date.now(); let previousRafDate = Date.now();
const { pause: pauseRaf, resume: resumeRaf } = useRafFn( const { pause: pauseRaf, resume: resumeRaf } = useRafFn(
@ -37,14 +38,14 @@ function pause() {
</c-card> </c-card>
<div mt-5 flex justify-center gap-3> <div mt-5 flex justify-center gap-3>
<c-button v-if="!isRunning" type="primary" @click="resume"> <c-button v-if="!isRunning" type="primary" @click="resume">
Start {{ t('tools.chronometer.button.start') }}
</c-button> </c-button>
<c-button v-else type="warning" @click="pause"> <c-button v-else type="warning" @click="pause">
Stop {{ t('tools.chronometer.button.stop') }}
</c-button> </c-button>
<c-button @click="counter = 0"> <c-button @click="counter = 0">
Reset {{ t('tools.chronometer.button.reset') }}
</c-button> </c-button>
</div> </div>
</div> </div>

View file

@ -3,6 +3,7 @@ import { normalizeEmail } from 'email-normalizer';
import { withDefaultOnError } from '@/utils/defaults'; import { withDefaultOnError } from '@/utils/defaults';
import { useCopy } from '@/composable/copy'; import { useCopy } from '@/composable/copy';
const { t } = useI18n();
const emails = ref(''); const emails = ref('');
const normalizedEmails = computed(() => { const normalizedEmails = computed(() => {
if (!emails.value) { if (!emails.value) {
@ -17,17 +18,17 @@ const normalizedEmails = computed(() => {
.join('\n'); .join('\n');
}); });
const { copy } = useCopy({ source: normalizedEmails, text: 'Normalized emails copied to the clipboard', createToast: true }); const { copy } = useCopy({ source: normalizedEmails, text: t('tools.email-normalizer.copied'), createToast: true });
</script> </script>
<template> <template>
<div> <div>
<div class="mb-2"> <div class="mb-2">
Raw emails to normalize: {{ t('tools.email-normalizer.input') }}
</div> </div>
<c-input-text <c-input-text
v-model:value="emails" v-model:value="emails"
placeholder="Put your emails here (one per line)..." :placeholder="t('tools.email-normalizer.input-placeholder')"
rows="3" rows="3"
multiline multiline
autocomplete="off" autocomplete="off"
@ -39,11 +40,11 @@ const { copy } = useCopy({ source: normalizedEmails, text: 'Normalized emails co
/> />
<div class="mb-2 mt-4"> <div class="mb-2 mt-4">
Normalized emails: {{ t('tools.email-normalizer.output') }}
</div> </div>
<c-input-text <c-input-text
:value="normalizedEmails" :value="normalizedEmails"
placeholder="Normalized emails will appear here..." :placeholder="t('tools.email-normalizer.output-placeholder')"
rows="3" rows="3"
autocomplete="off" autocomplete="off"
autocorrect="off" autocorrect="off"
@ -55,10 +56,10 @@ const { copy } = useCopy({ source: normalizedEmails, text: 'Normalized emails co
/> />
<div class="mt-4 flex justify-center gap-2"> <div class="mt-4 flex justify-center gap-2">
<c-button @click="emails = ''"> <c-button @click="emails = ''">
Clear emails {{ t('tools.email-normalizer.button.clear') }}
</c-button> </c-button>
<c-button :disabled="!normalizedEmails" @click="copy()"> <c-button :disabled="!normalizedEmails" @click="copy()">
Copy normalized emails {{ t('tools.email-normalizer.button.copy') }}
</c-button> </c-button>
</div> </div>
</div> </div>

View file

@ -1,10 +1,11 @@
import { Mail } from '@vicons/tabler'; import { Mail } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'Email normalizer', name: translate('tools.email-normalizer.title'),
path: '/email-normalizer', path: '/email-normalizer',
description: 'Normalize email addresses to a standard format for easier comparison. Useful for deduplication and data cleaning.', description: translate('tools.email-normalizer.description'),
keywords: ['email', 'normalizer'], keywords: ['email', 'normalizer'],
component: () => import('./email-normalizer.vue'), component: () => import('./email-normalizer.vue'),
icon: Mail, icon: Mail,

View file

@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler'; import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'JSON to XML', name: translate('tools.json-to-xml.title'),
path: '/json-to-xml', path: '/json-to-xml',
description: 'Convert JSON to XML', description: translate('tools.json-to-xml.description'),
keywords: ['json', 'xml'], keywords: ['json', 'xml'],
component: () => import('./json-to-xml.vue'), component: () => import('./json-to-xml.vue'),
icon: Braces, icon: Braces,

View file

@ -4,6 +4,7 @@ import JSON5 from 'json5';
import { withDefaultOnError } from '@/utils/defaults'; import { withDefaultOnError } from '@/utils/defaults';
import type { UseValidationRule } from '@/composable/validation'; import type { UseValidationRule } from '@/composable/validation';
const { t } = useI18n();
const defaultValue = '{"a":{"_attributes":{"x":"1.234","y":"It\'s"}}}'; const defaultValue = '{"a":{"_attributes":{"x":"1.234","y":"It\'s"}}}';
function transformer(value: string) { function transformer(value: string) {
return withDefaultOnError(() => { return withDefaultOnError(() => {
@ -14,17 +15,17 @@ function transformer(value: string) {
const rules: UseValidationRule<string>[] = [ const rules: UseValidationRule<string>[] = [
{ {
validator: (v: string) => v === '' || JSON5.parse(v), validator: (v: string) => v === '' || JSON5.parse(v),
message: 'Provided JSON is not valid.', message: t('tools.json-to-xml.error'),
}, },
]; ];
</script> </script>
<template> <template>
<format-transformer <format-transformer
input-label="Your JSON content" :input-label="t('tools.json-to-xml.input')"
:input-default="defaultValue" :input-default="defaultValue"
input-placeholder="Paste your JSON content here..." :input-placeholder="t('tools.json-to-xml.input-placeholder')"
output-label="Converted XML" :output-label="t('tools.json-to-xml.output')"
output-language="xml" output-language="xml"
:transformer="transformer" :transformer="transformer"
:input-validation-rules="rules" :input-validation-rules="rules"

View file

@ -1,10 +1,11 @@
import { Markdown } from '@vicons/tabler'; import { Markdown } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'Markdown to HTML', name: translate('tools.markdown-to-html.title'),
path: '/markdown-to-html', path: '/markdown-to-html',
description: 'Convert Markdown to Html and allow to print (as PDF)', description: translate('tools.markdown-to-html.description'),
keywords: ['markdown', 'html', 'converter', 'pdf'], keywords: ['markdown', 'html', 'converter', 'pdf'],
component: () => import('./markdown-to-html.vue'), component: () => import('./markdown-to-html.vue'),
icon: Markdown, icon: Markdown,

View file

@ -7,6 +7,7 @@ const outputHtml = computed(() => {
const md = markdownit(); const md = markdownit();
return md.render(inputMarkdown.value); return md.render(inputMarkdown.value);
}); });
const { t } = useI18n();
function printHtml() { function printHtml() {
const w = window.open(); const w = window.open();
@ -23,21 +24,21 @@ function printHtml() {
<c-input-text <c-input-text
v-model:value="inputMarkdown" v-model:value="inputMarkdown"
multiline raw-text multiline raw-text
placeholder="Your Markdown content..." :placeholder="t('tools.markdown-to-html.markdownInput')"
rows="8" rows="8"
autofocus autofocus
label="Your Markdown to convert:" :label="t('tools.markdown-to-html.markdown')"
/> />
<n-divider /> <n-divider />
<n-form-item label="Output HTML:"> <n-form-item :label="t('tools.markdown-to-html.html')">
<TextareaCopyable :value="outputHtml" :word-wrap="true" language="html" /> <TextareaCopyable :value="outputHtml" :word-wrap="true" language="html" />
</n-form-item> </n-form-item>
<div flex justify-center> <div flex justify-center>
<n-button @click="printHtml"> <n-button @click="printHtml">
Print as PDF {{ t('tools.markdown-to-html.button.print') }}
</n-button> </n-button>
</div> </div>
</div> </div>

View file

@ -3,6 +3,7 @@ import type { QRCodeErrorCorrectionLevel } from 'qrcode';
import { useQRCode } from './useQRCode'; import { useQRCode } from './useQRCode';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64'; import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
const { t } = useI18n();
const foreground = ref('#000000ff'); const foreground = ref('#000000ff');
const background = ref('#ffffffff'); const background = ref('#ffffffff');
const errorCorrectionLevel = ref<QRCodeErrorCorrectionLevel>('medium'); const errorCorrectionLevel = ref<QRCodeErrorCorrectionLevel>('medium');
@ -32,23 +33,23 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
label-position="left" label-position="left"
label-width="130px" label-width="130px"
label-align="right" label-align="right"
label="Text:" :label="t('tools.qrcode-generator.text')"
multiline multiline
rows="1" rows="1"
autosize autosize
placeholder="Your link or text..." :placeholder="t('tools.qrcode-generator.placeholder')"
mb-6 mb-6
/> />
<n-form label-width="130" label-placement="left"> <n-form label-width="130" label-placement="left">
<n-form-item label="Foreground color:"> <n-form-item :label="t('tools.qrcode-generator.foreground-color')">
<n-color-picker v-model:value="foreground" :modes="['hex']" /> <n-color-picker v-model:value="foreground" :modes="['hex']" />
</n-form-item> </n-form-item>
<n-form-item label="Background color:"> <n-form-item :label="t('tools.qrcode-generator.background-color')">
<n-color-picker v-model:value="background" :modes="['hex']" /> <n-color-picker v-model:value="background" :modes="['hex']" />
</n-form-item> </n-form-item>
<c-select <c-select
v-model:value="errorCorrectionLevel" v-model:value="errorCorrectionLevel"
label="Error resistance:" :label="t('tools.qrcode-generator.error-resistance')"
label-position="left" label-position="left"
label-width="130px" label-width="130px"
label-align="right" label-align="right"
@ -60,7 +61,7 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
<div flex flex-col items-center gap-3> <div flex flex-col items-center gap-3>
<n-image :src="qrcode" width="200" /> <n-image :src="qrcode" width="200" />
<c-button @click="download"> <c-button @click="download">
Download qr-code {{ t('tools.qrcode-generator.button.download') }}
</c-button> </c-button>
</div> </div>
</n-gi> </n-gi>

View file

@ -1,10 +1,11 @@
import { BrandJavascript } from '@vicons/tabler'; import { BrandJavascript } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'Regex cheatsheet', name: translate('tools.regex-memo.title'),
path: '/regex-memo', path: '/regex-memo',
description: 'Javascript Regex/Regular Expression cheatsheet', description: translate('tools.regex-memo.description'),
keywords: ['regex', 'regular', 'expression', 'javascript', 'memo', 'cheatsheet'], keywords: ['regex', 'regular', 'expression', 'javascript', 'memo', 'cheatsheet'],
component: () => import('./regex-memo.vue'), component: () => import('./regex-memo.vue'),
icon: BrandJavascript, icon: BrandJavascript,

View file

@ -1,10 +1,11 @@
import { Language } from '@vicons/tabler'; import { Language } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'Regex Tester', name: translate('tools.regex-tester.title'),
path: '/regex-tester', path: '/regex-tester',
description: 'Test your regular expressions with sample text.', description: translate('tools.regex-tester.description'),
keywords: ['regex', 'tester', 'sample', 'expression'], keywords: ['regex', 'tester', 'sample', 'expression'],
component: () => import('./regex-tester.vue'), component: () => import('./regex-tester.vue'),
icon: Language, icon: Language,

View file

@ -15,6 +15,7 @@ const dotAll = ref(true);
const unicode = ref(true); const unicode = ref(true);
const unicodeSets = ref(false); const unicodeSets = ref(false);
const visualizerSVG = ref<ShadowRootExpose>(); const visualizerSVG = ref<ShadowRootExpose>();
const { t } = useI18n();
const regexValidation = useValidation({ const regexValidation = useValidation({
source: regex, source: regex,
@ -92,36 +93,36 @@ watchEffect(
<template> <template>
<div max-w-600px> <div max-w-600px>
<c-card title="Regex" mb-1> <c-card :title="t('tools.regex-tester.regex')" mb-1>
<c-input-text <c-input-text
v-model:value="regex" v-model:value="regex"
label="Regex to test:" :label="t('tools.regex-tester.regex-input')"
placeholder="Put the regex to test" :placeholder="t('tools.regex-tester.regex-input-placeholder')"
multiline multiline
rows="3" rows="3"
:validation="regexValidation" :validation="regexValidation"
/> />
<router-link target="_blank" to="/regex-memo" mb-1 mt-1> <router-link target="_blank" to="/regex-memo" mb-1 mt-1>
See Regular Expression Cheatsheet {{ t('tools.regex-tester.link') }}
</router-link> </router-link>
<n-space> <n-space>
<n-checkbox v-model:checked="global"> <n-checkbox v-model:checked="global">
<span title="Global search">Global search. (<code>g</code>)</span> <span :title="t('tools.regex-tester.global')">Global search (<code>g</code>)</span>
</n-checkbox> </n-checkbox>
<n-checkbox v-model:checked="ignoreCase"> <n-checkbox v-model:checked="ignoreCase">
<span title="Case-insensitive search">Case-insensitive search. (<code>i</code>)</span> <span :title="t('tools.regex-tester.ignoreCase')">Case-insensitive search (<code>i</code>)</span>
</n-checkbox> </n-checkbox>
<n-checkbox v-model:checked="multiline"> <n-checkbox v-model:checked="multiline">
<span title="Allows ^ and $ to match next to newline characters.">Multiline(<code>m</code>)</span> <span :title="t('tools.regex-tester.multiline')">Multiline (<code>m</code>)</span>
</n-checkbox> </n-checkbox>
<n-checkbox v-model:checked="dotAll"> <n-checkbox v-model:checked="dotAll">
<span title="Allows . to match newline characters.">Singleline(<code>s</code>)</span> <span :title="t('tools.regex-tester.dotAll')">Singleline (<code>s</code>)</span>
</n-checkbox> </n-checkbox>
<n-checkbox v-model:checked="unicode"> <n-checkbox v-model:checked="unicode">
<span title="Unicode; treat a pattern as a sequence of Unicode code points.">Unicode(<code>u</code>)</span> <span :title="t('tools.regex-tester.unicode')">Unicode (<code>u</code>)</span>
</n-checkbox> </n-checkbox>
<n-checkbox v-model:checked="unicodeSets"> <n-checkbox v-model:checked="unicodeSets">
<span title="An upgrade to the u mode with more Unicode features.">Unicode Sets (<code>v</code>)</span> <span :title="t('tools.regex-tester.unicodeSets')">Unicode Sets (<code>v</code>)</span>
</n-checkbox> </n-checkbox>
</n-space> </n-space>
@ -129,28 +130,28 @@ watchEffect(
<c-input-text <c-input-text
v-model:value="text" v-model:value="text"
label="Text to match:" :label="t('tools.regex-tester.text-input')"
placeholder="Put the text to match" :placeholder="t('tools.regex-tester.text-input-placeholder')"
multiline multiline
rows="5" rows="5"
/> />
</c-card> </c-card>
<c-card title="Matches" mb-1 mt-3> <c-card :title="t('tools.regex-tester.matches')" mb-1 mt-3>
<n-table v-if="results?.length > 0"> <n-table v-if="results?.length > 0">
<thead> <thead>
<tr> <tr>
<th scope="col"> <th scope="col">
Index in text {{ t('tools.regex-tester.text-index') }}
</th> </th>
<th scope="col"> <th scope="col">
Value {{ t('tools.regex-tester.value') }}
</th> </th>
<th scope="col"> <th scope="col">
Captures {{ t('tools.regex-tester.captures') }}
</th> </th>
<th scope="col"> <th scope="col">
Groups {{ t('tools.regex-tester.groups') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@ -176,15 +177,15 @@ watchEffect(
</tbody> </tbody>
</n-table> </n-table>
<c-alert v-else> <c-alert v-else>
No match {{ t('tools.regex-tester.no-match') }}
</c-alert> </c-alert>
</c-card> </c-card>
<c-card title="Sample matching text" mt-3> <c-card :title="t('tools.regex-tester.sample')" mt-3>
<pre style="white-space: pre-wrap; word-break: break-all;">{{ sample }}</pre> <pre style="white-space: pre-wrap; word-break: break-all;">{{ sample }}</pre>
</c-card> </c-card>
<c-card title="Regex Diagram" style="overflow-x: scroll;" mt-3> <c-card :title="t('tools.regex-tester.diagram')" style="overflow-x: scroll;" mt-3>
<shadow-root ref="visualizerSVG"> <shadow-root ref="visualizerSVG">
&#xa0; &#xa0;
</shadow-root> </shadow-root>

View file

@ -1,10 +1,11 @@
import { Mailbox } from '@vicons/tabler'; import { Mailbox } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'Outlook Safelink decoder', name: translate('tools.safelink-decoder.title'),
path: '/safelink-decoder', path: '/safelink-decoder',
description: 'Decode Outlook SafeLink links', description: translate('tools.safelink-decoder.description'),
keywords: ['outlook', 'safelink', 'decoder'], keywords: ['outlook', 'safelink', 'decoder'],
component: () => import('./safelink-decoder.vue'), component: () => import('./safelink-decoder.vue'),
icon: Mailbox, icon: Mailbox,

View file

@ -2,6 +2,7 @@
import { decodeSafeLinksURL } from './safelink-decoder.service'; import { decodeSafeLinksURL } from './safelink-decoder.service';
import TextareaCopyable from '@/components/TextareaCopyable.vue'; import TextareaCopyable from '@/components/TextareaCopyable.vue';
const { t } = useI18n();
const inputSafeLinkUrl = ref(''); const inputSafeLinkUrl = ref('');
const outputDecodedUrl = computed(() => { const outputDecodedUrl = computed(() => {
try { try {
@ -18,14 +19,14 @@ const outputDecodedUrl = computed(() => {
<c-input-text <c-input-text
v-model:value="inputSafeLinkUrl" v-model:value="inputSafeLinkUrl"
raw-text raw-text
placeholder="Your input Outlook SafeLink Url..." :placeholder="t('tools.safelink-decoder.input-placeholder')"
autofocus autofocus
label="Your input Outlook SafeLink Url:" :label="t('tools.safelink-decoder.input')"
/> />
<n-divider /> <n-divider />
<n-form-item label="Output decoded URL:"> <n-form-item :label="t('tools.safelink-decoder.output')">
<TextareaCopyable :value="outputDecodedUrl" :word-wrap="true" /> <TextareaCopyable :value="outputDecodedUrl" :word-wrap="true" />
</n-form-item> </n-form-item>
</div> </div>

View file

@ -3,17 +3,18 @@ import { getStringSizeInBytes } from './text-statistics.service';
import { formatBytes } from '@/utils/convert'; import { formatBytes } from '@/utils/convert';
const text = ref(''); const text = ref('');
const { t } = useI18n();
</script> </script>
<template> <template>
<c-card> <c-card>
<c-input-text v-model:value="text" multiline placeholder="Your text..." rows="5" /> <c-input-text v-model:value="text" multiline :placeholder="t('tools.text-statistics.placeholder')" rows="5" />
<div mt-5 flex> <div mt-5 flex>
<n-statistic label="Character count" :value="text.length" flex-1 /> <n-statistic :label="t('tools.text-statistics.characters')" :value="text.length" flex-1 />
<n-statistic label="Word count" :value="text === '' ? 0 : text.split(/\s+/).length" flex-1 /> <n-statistic :label="t('tools.text-statistics.words')" :value="text === '' ? 0 : text.split(/\s+/).length" flex-1 />
<n-statistic label="Line count" :value="text === '' ? 0 : text.split(/\r\n|\r|\n/).length" flex-1 /> <n-statistic :label="t('tools.text-statistics.lines')" :value="text === '' ? 0 : text.split(/\r\n|\r|\n/).length" flex-1 />
<n-statistic label="Byte size" :value="formatBytes(getStringSizeInBytes(text))" flex-1 /> <n-statistic :label="t('tools.text-statistics.bytes')" :value="formatBytes(getStringSizeInBytes(text))" flex-1 />
</div> </div>
</c-card> </c-card>
</template> </template>

View file

@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler'; import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'XML to JSON', name: translate('tools.xml-to-json.title'),
path: '/xml-to-json', path: '/xml-to-json',
description: 'Convert XML to JSON', description: translate('tools.xml-to-json.description'),
keywords: ['xml', 'json'], keywords: ['xml', 'json'],
component: () => import('./xml-to-json.vue'), component: () => import('./xml-to-json.vue'),
icon: Braces, icon: Braces,

View file

@ -4,6 +4,7 @@ import { isValidXML } from '../xml-formatter/xml-formatter.service';
import { withDefaultOnError } from '@/utils/defaults'; import { withDefaultOnError } from '@/utils/defaults';
import type { UseValidationRule } from '@/composable/validation'; import type { UseValidationRule } from '@/composable/validation';
const { t } = useI18n();
const defaultValue = '<a x="1.234" y="It\'s"/>'; const defaultValue = '<a x="1.234" y="It\'s"/>';
function transformer(value: string) { function transformer(value: string) {
return withDefaultOnError(() => { return withDefaultOnError(() => {
@ -14,17 +15,17 @@ function transformer(value: string) {
const rules: UseValidationRule<string>[] = [ const rules: UseValidationRule<string>[] = [
{ {
validator: isValidXML, validator: isValidXML,
message: 'Provided XML is not valid.', message: t('tools.xml-to-json.error'),
}, },
]; ];
</script> </script>
<template> <template>
<format-transformer <format-transformer
input-label="Your XML content" :input-label="t('tools.xml-to-json.input')"
:input-default="defaultValue" :input-default="defaultValue"
input-placeholder="Paste your XML content here..." :input-placeholder="t('tools.xml-to-json.input-placeholder')"
output-label="Converted JSON" :output-label="t('tools.xml-to-json.output')"
output-language="json" output-language="json"
:transformer="transformer" :transformer="transformer"
:input-validation-rules="rules" :input-validation-rules="rules"