WIP(translate): Completed some page translation under web category

This commit is contained in:
Amery2010 2024-01-10 23:15:39 +08:00
parent 2da11a7242
commit c6583ef013
90 changed files with 1112 additions and 341 deletions

View file

@ -7,14 +7,17 @@ import BaseLayout from './base.layout.vue';
import FavoriteButton from '@/components/FavoriteButton.vue';
import type { Tool } from '@/tools/tools.types';
const { t } = useI18n();
const route = useRoute();
const i18nKey = computed<string>(() => route.path.trim().replace('/', ''));
const head = computed<HeadObject>(() => ({
title: `${route.meta.name} - IT Tools`,
title: `${t(`tools.${i18nKey.value}.title`)} - IT Tools`,
meta: [
{
name: 'description',
content: route.meta?.description as string,
content: t(`tools.${i18nKey.value}.description`),
},
{
name: 'keywords',
@ -23,9 +26,7 @@ const head = computed<HeadObject>(() => ({
],
}));
useHead(head);
const { t } = useI18n();
const i18nKey = computed<string>(() => route.path.trim().replace('/', ''));
const toolTitle = computed<string>(() => t(`tools.${i18nKey.value}.title`, String(route.meta.name)));
const toolDescription = computed<string>(() => t(`tools.${i18nKey.value}.description`, String(route.meta.description)));
</script>

View file

@ -1,11 +1,11 @@
import { FileDigit } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.base64-file-converter.title'),
name: t('tools.base64-file-converter.title'),
path: '/base64-file-converter',
description: translate('tools.base64-file-converter.description'),
description: t('tools.base64-file-converter.description'),
keywords: ['base64', 'converter', 'upload', 'image', 'file', 'conversion', 'web', 'data', 'format'],
component: () => import('./base64-file-converter.vue'),
icon: FileDigit,

View file

@ -1,18 +1,18 @@
tools:
base64-file-converter:
title: Base64 文件转换器
description: 将字符串、文件或图像转换为其 base64 表示形式。
title: Base64 file converter
description: Convert string, files or images into a it's base64 representation.
base64ToFile:
title: Base64 转文件
placeholder: 在此处放置您的 base64 文件字符串...
title: Base64 to file
placeholder: Put your base64 file string here...
fileToBase64:
title: 文件转 base64
placeholder: base64 文件将在此处
uploadTip: 拖放文件到此处,或点击选择文件
title: File to base64
placeholder: File in base64 will be here
uploadTip: Drag and drop a file here, or click to select a file
buttons:
downloadFile: 下载文件
copy: 复制
downloadFile: Download file
copy: Copy
copied: 已复制 base64 字符串到剪贴板
invalidMessage: 无效的 base 64 字符串
copied: Base64 string copied to the clipboard
invalidMessage: Invalid base 64 string

View file

@ -0,0 +1,18 @@
tools:
base64-file-converter:
title: Base64 文件转换器
description: 将字符串、文件或图像转换为其 base64 表示形式。
base64ToFile:
title: Base64 转文件
placeholder: 在此处放置您的 base64 文件字符串...
fileToBase64:
title: 文件转 base64
placeholder: base64 文件将在此处
uploadTip: 拖放文件到此处,或点击选择文件
buttons:
downloadFile: 下载文件
copy: 复制
copied: 已复制 base64 字符串到剪贴板
invalidMessage: 无效的 base 64 字符串

View file

@ -1,11 +1,11 @@
import { FileDigit } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.base64-string-converter.title'),
name: t('tools.base64-string-converter.title'),
path: '/base64-string-converter',
description: translate('tools.base64-string-converter.description'),
description: t('tools.base64-string-converter.description'),
keywords: ['base64', 'converter', 'conversion', 'web', 'data', 'format', 'atob', 'btoa'],
component: () => import('./base64-string-converter.vue'),
icon: FileDigit,

View file

@ -2,20 +2,21 @@
import { useCopy } from '@/composable/copy';
import { textToBase64 } from '@/utils/base64';
const { t } = useI18n();
const username = ref('');
const password = ref('');
const header = computed(() => `Authorization: Basic ${textToBase64(`${username.value}:${password.value}`)}`);
const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard' });
const { copy } = useCopy({ source: header, text: t('tools.basic-auth-generator.copied') });
</script>
<template>
<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.inputLabel')" :placeholder="t('tools.basic-auth-generator.username.inputPlaceholder')" clearable raw-text mb-5 />
<c-input-text
v-model:value="password"
label="Password"
placeholder="Your password..."
:label="t('tools.basic-auth-generator.password.inputLabel')"
:placeholder="t('tools.basic-auth-generator.password.inputPlaceholder')"
clearable
raw-text
mb-2
@ -23,7 +24,7 @@ const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard
/>
<c-card>
<n-statistic label="Authorization header:" class="header">
<n-statistic :label="t('tools.basic-auth-generator.outputTitle')" class="header">
<n-scrollbar x-scrollable style="max-width: 550px; margin-bottom: -10px; padding-bottom: 10px" trigger="none">
{{ header }}
</n-scrollbar>
@ -31,7 +32,7 @@ const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard
</c-card>
<div mt-5 flex justify-center>
<c-button @click="copy()">
Copy header
{{ t('tools.basic-auth-generator.copyHeader') }}
</c-button>
</div>
</div>

View file

@ -1,10 +1,11 @@
import { PasswordRound } from '@vicons/material';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Basic auth generator',
name: t('tools.basic-auth-generator.title'),
path: '/basic-auth-generator',
description: 'Generate a base64 basic auth header from an username and a password.',
description: t('tools.basic-auth-generator.description'),
keywords: [
'basic',
'auth',

View file

@ -0,0 +1,15 @@
tools:
basic-auth-generator:
title: Basic auth generator
description: Generate a base64 basic auth header from an username and a password.
username:
inputLabel: Username
inputPlaceholder: Your username...
password:
inputLabel: Password
inputPlaceholder: Your password...
outputTitle: 'Authorization header:'
copyHeader: Copy header
copied: Header copied to the clipboard

View file

@ -0,0 +1,15 @@
tools:
basic-auth-generator:
title: Basic auth 生成器
description: 从用户名和密码生成一个 base64 基本授权头。
username:
inputLabel: 用户名
inputPlaceholder: 您的用户名...
password:
inputLabel: 密码
inputPlaceholder: 您的密码...
outputTitle: '授权头:'
copyHeader: 复制授权头
copied: 授权头已复制到剪贴板

View file

@ -1,11 +1,11 @@
import { LockSquare } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.bcrypt.title'),
name: t('tools.bcrypt.title'),
path: '/bcrypt',
description: translate('tools.bcrypt.description'),
description: t('tools.bcrypt.description'),
keywords: ['bcrypt', 'hash', 'compare', 'password', 'salt', 'round', 'storage', 'crypto'],
component: () => import('./bcrypt.vue'),
icon: LockSquare,

View file

@ -1,11 +1,11 @@
import { AlignJustified } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.bip39-generator.title'),
name: t('tools.bip39-generator.title'),
path: '/bip39-generator',
description: translate('tools.bip39-generator.description'),
description: t('tools.bip39-generator.description'),
keywords: ['BIP39', 'passphrase', 'generator', 'mnemonic', 'entropy'],
component: () => import('./bip39-generator.vue'),
icon: AlignJustified,

View file

@ -1,11 +1,11 @@
import { LetterCaseToggle } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.case-converter.title'),
name: t('tools.case-converter.title'),
path: '/case-converter',
description: translate('tools.case-converter.description'),
description: t('tools.case-converter.description'),
keywords: [
'case',
'converter',

View file

@ -1,7 +1,7 @@
import { type Colord, colord } from 'colord';
import { withDefaultOnError } from '@/utils/defaults';
import { useValidation } from '@/composable/validation';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export { removeAlphaChannelWhenOpaque, buildColorFormat };
@ -14,7 +14,7 @@ function buildColorFormat({
parse = value => colord(value),
format,
placeholder,
invalidMessage = translate('tools.color-converter.invalidMessage', { format: label.toLowerCase() }),
invalidMessage = t('tools.color-converter.invalidMessage', { format: label.toLowerCase() }),
type = 'text',
}: {
label: string

View file

@ -1,11 +1,11 @@
import { Palette } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.color-converter.title'),
name: t('tools.color-converter.title'),
path: '/color-converter',
description: translate('tools.color-converter.description'),
description: t('tools.color-converter.description'),
keywords: ['color', 'converter'],
component: () => import('./color-converter.vue'),
icon: Palette,

View file

@ -1,11 +1,11 @@
import { Calendar } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.date-converter.title'),
name: t('tools.date-converter.title'),
path: '/date-converter',
description: translate('tools.date-converter.description'),
description: t('tools.date-converter.description'),
keywords: ['date', 'time', 'converter', 'iso', 'utc', 'timezone', 'year', 'month', 'day', 'minute', 'seconde'],
component: () => import('./date-time-converter.vue'),
icon: Calendar,

View file

@ -1,55 +1,56 @@
<script setup lang="ts">
import { useWindowSize } from '@vueuse/core';
const { t } = useI18n();
const { width, height } = useWindowSize();
const sections = [
{
name: 'Screen',
name: t('tools.device-information.screen'),
information: [
{
label: 'Screen size',
label: t('tools.device-information.screenSize'),
value: computed(() => `${window.screen.availWidth} x ${window.screen.availHeight}`),
},
{
label: 'Orientation',
label: t('tools.device-information.orientation'),
value: computed(() => window.screen.orientation.type),
},
{
label: 'Orientation angle',
label: t('tools.device-information.orientationAngle'),
value: computed(() => `${window.screen.orientation.angle}°`),
},
{
label: 'Color depth',
label: t('tools.device-information.colorDepth'),
value: computed(() => `${window.screen.colorDepth} bits`),
},
{
label: 'Pixel ratio',
label: t('tools.device-information.pixelRatio'),
value: computed(() => `${window.devicePixelRatio} dppx`),
},
{
label: 'Window size',
label: t('tools.device-information.windowSize'),
value: computed(() => `${width.value} x ${height.value}`),
},
],
},
{
name: 'Device',
name: t('tools.device-information.device'),
information: [
{
label: 'Browser vendor',
label: t('tools.device-information.browserVendor'),
value: computed(() => navigator.vendor),
},
{
label: 'Languages',
label: t('tools.device-information.languages'),
value: computed(() => navigator.languages.join(', ')),
},
{
label: 'Platform',
label: t('tools.device-information.platform'),
value: computed(() => navigator.platform),
},
{
label: 'User agent',
label: t('tools.device-information.userAgent'),
value: computed(() => navigator.userAgent),
},
],

View file

@ -1,10 +1,11 @@
import { DeviceDesktop } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Device information',
name: t('tools.device-information.title'),
path: '/device-information',
description: 'Get information about your current device (screen size, pixel-ratio, user agent, ...)',
description: t('tools.device-information.description'),
keywords: [
'device',
'information',

View file

@ -0,0 +1,17 @@
tools:
device-information:
title: Device information
description: Get information about your current device (screen size, pixel-ratio, user agent, ...)
screen: Screen
screenSize: Screen size
orientation: Orientation
orientationAngle: Orientation angle
colorDepth: Color depth
pixelRatio: Pixel ratio
windowSize: Window size
device: Device
browserVendor: Browser vendor
languages: Languages
platform: Platform
userAgent: User agent

View file

@ -0,0 +1,17 @@
tools:
device-information:
title: 设备信息
description: 获取关于您当前设备的信息(屏幕大小、像素比、用户代理...
screen: 屏幕
screenSize: 屏幕尺寸
orientation: 方向
orientationAngle: 方向角度
colorDepth: 色彩深度
pixelRatio: 像素比
windowSize: 窗口大小
device: 设备
browserVendor: 浏览器供应商
languages: 语言
platform: 平台
userAgent: 用户代理

View file

@ -1,11 +1,11 @@
import { Lock } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.encryption.title'),
name: t('tools.encryption.title'),
path: '/encryption',
description: translate('tools.encryption.description'),
description: t('tools.encryption.description'),
keywords: ['cypher', 'encipher', 'text', 'AES', 'TripleDES', 'Rabbit', 'RC4'],
component: () => import('./encryption.vue'),
icon: Lock,

View file

@ -1,11 +1,11 @@
import { EyeOff } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.hash-text.title'),
name: t('tools.hash-text.title'),
path: '/hash-text',
description: translate('tools.hash-text.description'),
description: t('tools.hash-text.description'),
keywords: [
'hash',
'digest',

View file

@ -1,11 +1,11 @@
import { ShortTextRound } from '@vicons/material';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.hmac-generator.title'),
name: t('tools.hmac-generator.title'),
path: '/hmac-generator',
description: translate('tools.hmac-generator.description'),
description: t('tools.hmac-generator.description'),
keywords: ['hmac', 'generator', 'MD5', 'SHA1', 'SHA256', 'SHA224', 'SHA512', 'SHA384', 'SHA3', 'RIPEMD160'],
component: () => import('./hmac-generator.vue'),
icon: ShortTextRound,

View file

@ -3,6 +3,7 @@ import { escape, unescape } from 'lodash';
import { useCopy } from '@/composable/copy';
const { t } = useI18n();
const escapeInput = ref('<title>IT Tool</title>');
const escapeOutput = computed(() => escape(escapeInput.value));
const { copy: copyEscaped } = useCopy({ source: escapeOutput });
@ -13,23 +14,23 @@ const { copy: copyUnescaped } = useCopy({ source: unescapeOutput });
</script>
<template>
<c-card title="Escape html entities">
<n-form-item label="Your string :">
<c-card :title="t('tools.html-entities.escape.title')">
<n-form-item :label="t('tools.html-entities.escape.inputLabel')">
<c-input-text
v-model:value="escapeInput"
multiline
placeholder="The string to escape"
:placeholder="t('tools.html-entities.escape.inputPlaceholder')"
rows="3"
autosize
raw-text
/>
</n-form-item>
<n-form-item label="Your string escaped :">
<n-form-item :label="t('tools.html-entities.escape.outputLabel')">
<c-input-text
multiline
readonly
placeholder="Your string escaped"
:placeholder="t('tools.html-entities.escape.outputPlaceholder')"
:value="escapeOutput"
rows="3"
autosize
@ -38,28 +39,28 @@ const { copy: copyUnescaped } = useCopy({ source: unescapeOutput });
<div flex justify-center>
<c-button @click="copyEscaped()">
Copy
{{ t('tools.html-entities.copy') }}
</c-button>
</div>
</c-card>
<c-card title="Unescape html entities">
<n-form-item label="Your escaped string :">
<c-card :title="t('tools.html-entities.unescape.title')">
<n-form-item :label="t('tools.html-entities.unescape.inputLabel')">
<c-input-text
v-model:value="unescapeInput"
multiline
placeholder="The string to unescape"
:placeholder="t('tools.html-entities.unescape.inputPlaceholder')"
rows="3"
autosize
raw-text
/>
</n-form-item>
<n-form-item label="Your string unescaped :">
<n-form-item :label="t('tools.html-entities.unescape.outputLabel')">
<c-input-text
:value="unescapeOutput"
multiline
readonly
placeholder="Your string unescaped"
:placeholder="t('tools.html-entities.unescape.outputPlaceholder')"
rows="3"
autosize
/>
@ -67,7 +68,7 @@ const { copy: copyUnescaped } = useCopy({ source: unescapeOutput });
<div flex justify-center>
<c-button @click="copyUnescaped()">
Copy
{{ t('tools.html-entities.copy') }}
</c-button>
</div>
</c-card>

View file

@ -1,10 +1,11 @@
import { Code } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Escape html entities',
name: t('tools.html-entities.title'),
path: '/html-entities',
description: 'Escape or unescape html entities (replace <,>, &, " and \' to their html version)',
description: t('tools.html-entities.description'),
keywords: ['html', 'entities', 'escape', 'unescape', 'special', 'characters', 'tags'],
component: () => import('./html-entities.vue'),
icon: Code,

View file

@ -0,0 +1,19 @@
tools:
html-entities:
title: Escape html entities
description: Escape or unescape html entities (replace <,>, &, " and \' to their html version)
escape:
title: Escape html entities
inputLabel: 'Your string :'
inputPlaceholder: The string to escape
outputLabel: 'Your string escaped :'
outputPlaceholder: Your string escaped
unescape:
title: Unescape html entities
inputLabel: 'Your escaped string :'
inputPlaceholder: The string to unescape
outputLabel: 'Your string unescaped :'
outputPlaceholder: Your string unescaped
copy: Copy

View file

@ -0,0 +1,19 @@
tools:
html-entities:
title: 转义 HTML 实体
description: 转义或取消转义 HTML 实体(将 <,>, &, " 和 \' 替换为它们的 HTML 版本)
escape:
title: 转义 HTML 实体
inputLabel: '输入字符串:'
inputPlaceholder: 要转义的字符串
outputLabel: '转义后的字符串:'
outputPlaceholder: 转义后的字符串
unescape:
title: 取消转义 HTML 实体
inputLabel: '输入转义后的字符串:'
inputPlaceholder: 要取消转义的字符串
outputLabel: '取消转义后的字符串:'
outputPlaceholder: 取消转义后的字符串
copy: 复制

View file

@ -0,0 +1,4 @@
tools:
html-wysiwyg-editor:
title: HTML WYSIWYG editor
description: Online HTML editor with feature-rich WYSIWYG editor, get the source code of the content immediately.

View file

@ -0,0 +1,4 @@
tools:
http-status-codes:
title: HTTP status codes
description: The list of all HTTP status codes their name and their meaning.

View file

@ -93,11 +93,11 @@ export const toolsByCategory: ToolCategory[] = [
caseConverter,
textToNatoAlphabet,
textToBinary,
listConverter,
yamlToJson,
yamlToToml,
jsonToYaml,
jsonToToml,
listConverter,
tomlToJson,
tomlToYaml,
],

View file

@ -1,11 +1,11 @@
import { ArrowsLeftRight } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.base-converter.title'),
name: t('tools.base-converter.title'),
path: '/base-converter',
description: translate('tools.base-converter.description'),
description: t('tools.base-converter.description'),
keywords: ['integer', 'number', 'base', 'conversion', 'decimal', 'hexadecimal', 'binary', 'octal', 'base64'],
component: () => import('./integer-base-converter.vue'),
icon: ArrowsLeftRight,

View file

@ -1,4 +1,4 @@
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export function convertBase({ value, fromBase, toBase }: { value: string; fromBase: number; toBase: number }) {
const range = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/'.split('');
@ -9,7 +9,7 @@ export function convertBase({ value, fromBase, toBase }: { value: string; fromBa
.reverse()
.reduce((carry: number, digit: string, index: number) => {
if (!fromRange.includes(digit)) {
throw new Error(translate('tools.base-converter.invalidMessage', { digit, fromBase }));
throw new Error(t('tools.base-converter.invalidMessage', { digit, fromBase }));
}
return (carry += fromRange.indexOf(digit) * fromBase ** index);
}, 0);

View file

@ -0,0 +1,4 @@
tools:
json-diff:
title: JSON diff
description: Compare two JSON objects and get the differences between them.

View file

@ -1,11 +1,11 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.json-to-toml.title'),
name: t('tools.json-to-toml.title'),
path: '/json-to-toml',
description: translate('tools.json-to-toml.description'),
description: t('tools.json-to-toml.description'),
keywords: ['json', 'parse', 'toml', 'convert', 'transform'],
component: () => import('./json-to-toml.vue'),
icon: Braces,

View file

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

View file

@ -1,10 +1,11 @@
import { Key } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'JWT parser',
name: t('tools.jwt-parser.title'),
path: '/jwt-parser',
description: 'Parse and decode your JSON Web Token (jwt) and display its content.',
description: t('tools.jwt-parser.description'),
keywords: [
'jwt',
'parser',

View file

@ -0,0 +1,4 @@
tools:
jwt-parser:
title: JWT parser
description: Parse and decode your JSON Web Token (jwt) and display its content.

View file

@ -0,0 +1,4 @@
tools:
keycode-info:
title: Keycode info
description: Find the javascript keycode, code, location and modifiers of any pressed key.

View file

@ -1,11 +1,11 @@
import { List } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.list-converter.title'),
name: t('tools.list-converter.title'),
path: '/list-converter',
description: translate('tools.list-converter.description'),
description: t('tools.list-converter.description'),
keywords: ['list', 'converter', 'sort', 'reverse', 'prefix', 'suffix', 'lowercase', 'truncate'],
component: () => import('./list-converter.vue'),
icon: List,

View file

@ -1,10 +1,11 @@
import { Tags } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Open graph meta generator',
name: t('tools.og-meta-generator.title'),
path: '/og-meta-generator',
description: 'Generate open-graph and socials html meta tags for your website.',
description: t('tools.og-meta-generator.description'),
keywords: [
'meta',
'tag',

View file

@ -0,0 +1,199 @@
tools:
og-meta-generator:
title: Open graph meta generator
description: Generate open-graph and socials html meta tags for your website.
yourMetaTags: Your meta tags
article:
title: Article
publishingDate:
label: Publishing date
placeholder: When the article was first published...
modificationDate:
label: Modification date
placeholder: When the article was last changed...
expirationDate:
label: Expiration date
placeholder: When the article is out of date after...
author:
label: Author
placeholder: Writers of the article...
section:
label: Section
placeholder: A high-level section name. E.g. Technology..
tag:
label: Tag
placeholder: Tag words associated with this article...
book:
title: Book
author:
label: Author
placeholder: Who wrote this book...
ISBN:
label: ISBN
placeholder: The International Standard Book Number...
releaseDate:
label: Release date
placeholder: The date the book was released...
tag:
label: Tag
placeholder: Tag words associated with this book...
image:
title: Image
imageUrl:
label: Image url
placeholder: The url of your website social image...
imageAlt:
label: Image alt
placeholder: The alternative text of your website social image...
width:
label: Width
placeholder: Width in px of your website social image...
height:
label: Height
placeholder: Height in px of your website social image...
albumDetails:
title: Album details
song:
label: Song
placeholder: The song on this album...
disc:
label: Disc
placeholder: The same as music:album:disc but in reverse...
track:
label: Track
placeholder: The same as music:album:track but in reverse...
musician:
label: Musician
placeholder: The musician that made this song...
releaseDate:
label: Release date
placeholder: The date the album was released...
playlistDetails:
title: Playlist details
song:
label: Song
placeholder: The song on this album...
disc:
label: Disc
placeholder: The same as music:album:disc but in reverse...
track:
label: Track
placeholder: The same as music:album:track but in reverse...
creator:
label: Creator
placeholder: The creator of this playlist...
radioStationDetails:
title: Radio station details
creator:
label: Creator
placeholder: The creator of this radio station...
songDetails:
title: Song details
duration:
lebel: Duration
placeholder: The duration of the song...
album:
lebel: Album
placeholder: The album this song is from...
disc:
lebel: Disc
placeholder: Which disc of the album this song is on...
track:
lebel: Track
placeholder: Which track this song is...
musician:
lebel: Musician
placeholder: The musician that made this song...
profile:
title: Profile
firstName:
lebel: First name
placeholder: Enter the first name of the person...
lastName:
lebel: Last name
placeholder: Enter the last name of the person...
username:
lebel: Username
placeholder: Enter the username of the person...
gender:
lebel: Gender
placeholder: Enter the gender of the person...
twitter:
title: Twitter
card:
label: Card type
placeholder: The Twitter card type...
summary: Summary
summaryWithLargeImage: Summary with large image
application: Application
player: Player
site:
label: Site account
placeholder: 'The name of the Twitter account of the site (ex: @ittoolsdottech)...'
creator:
label: Creator acc.
placeholder: 'The name of the Twitter account of the creator (ex: @cthmsst)...'
videoEpisode:
title: Video episode details
series:
label: Series
placeholder: Which series this episode belongs to...
videoMovie:
title: Movie details
actor:
label: Actor
placeholder: Name of the actress/actor...
actorRole:
label: Actor role
placeholder: The role they played...
director:
label: Director
placeholder: Name of the director...
writer:
label: Writer
placeholder: Writers of the movie...
duration:
label: Duration
placeholder: The movie\'s length in seconds...
releaseDate:
label: Release date
placeholder: The date the movie was released...
tag:
label: Tag
placeholder: Tag words associated with this movie...
videoOther:
title: Other video details
videoTVShow:
title: TV show details
website:
title: General information
pageType:
label: Page type
placeholder: Select the type of your website...
pageTitle:
label: Title
placeholder: Enter the title of your website...
description:
label: Description
placeholder: Enter the description of your website...
url:
label: Page URL
placeholder: Enter the url of your website...
web: Website
article: Article
book: Book
profile: Profile
music:
label: Music
song: Song
musicAlbum: Music album
playlist: Playlist
radioStation: Radio station
video:
label: Music
movie: Movie
episode: Episode
tvShow: TV show
otherVideo: Other video

View file

@ -0,0 +1,199 @@
tools:
og-meta-generator:
title: 开放图谱元数据生成器
description: 为您的网站生成开放图谱和社交网站的 HTML 元标记。
yourMetaTags: 您的元标记
article:
title: 文章
publishingDate:
label: 发布日期
placeholder: 文章首次发布的日期...
modificationDate:
label: 修改日期
placeholder: 文章最后修改的日期...
expirationDate:
label: 过期日期
placeholder: 文章失效日期...
author:
label: 作者
placeholder: 文章的作者...
section:
label: 部分
placeholder: 高级部分名称。例如:技术...
tag:
label: 标签
placeholder: 与该文章相关的标签词...
book:
title: 书籍
author:
label: 作者
placeholder: 谁写了这本书...
ISBN:
label: ISBN
placeholder: 国际标准书号...
releaseDate:
label: 发行日期
placeholder: 书籍发布日期...
tag:
label: 标签
placeholder: 与该书籍相关的标签词...
image:
title: 图片
imageUrl:
label: 图片 URL
placeholder: 您网站社交图片的 URL...
imageAlt:
label: 图片说明
placeholder: 您网站社交图片的替代文本...
width:
label: 宽度
placeholder: 您网站社交图片的宽度(像素)...
height:
label: 高度
placeholder: 您网站社交图片的高度(像素)...
albumDetails:
title: 专辑详情
song:
label: 歌曲
placeholder: 该专辑中的歌曲...
disc:
label: 光盘
placeholder: 与音乐:专辑:光盘相同,但顺序相反...
track:
label: 曲目
placeholder: 与音乐:专辑:曲目相同,但顺序相反...
musician:
label: 音乐家
placeholder: 制作该歌曲的音乐家...
releaseDate:
label: 发行日期
placeholder: 专辑发布日期...
playlistDetails:
title: 播放列表详情
song:
label: 歌曲
placeholder: 该专辑中的歌曲...
disc:
label: 光盘
placeholder: 与音乐:专辑:光盘相同,但顺序相反...
track:
label: 曲目
placeholder: 与音乐:专辑:曲目相同,但顺序相反...
creator:
label: 创建者
placeholder: 此播放列表的创建者...
radioStationDetails:
title: 电台详情
creator:
label: 创建者
placeholder: 电台的创建者...
songDetails:
title: 歌曲详情
duration:
lebel: 时长
placeholder: 歌曲的时长...
album:
lebel: 专辑
placeholder: 歌曲所属的专辑...
disc:
lebel: 光盘
placeholder: 歌曲所在专辑的光盘...
track:
lebel: 曲目
placeholder: 歌曲的曲目...
musician:
lebel: 音乐家
placeholder: 制作该歌曲的音乐家...
profile:
title: 个人资料
firstName:
lebel: 名字
placeholder: 输入个人的名字...
lastName:
lebel: 姓氏
placeholder: 输入个人的姓氏...
username:
lebel: 用户名
placeholder: 输入个人的用户名...
gender:
lebel: 性别
placeholder: 输入个人的性别...
twitter:
title: 推特
card:
label: 卡片类型
placeholder: 推特卡片类型...
summary: 摘要
summaryWithLargeImage: 带大图的摘要
application: 应用
player: 播放器
site:
label: 网站账号
placeholder: '网站的推特账号(例如:@ittoolsdottech...'
creator:
label: 创建者账号
placeholder: '创建者的推特账号(例如:@cthmsst...'
videoEpisode:
title: 视频剧集详情
series:
label: 系列
placeholder: 该剧集所属的系列...
videoMovie:
title: 电影详情
actor:
label: 演员
placeholder: 女演员/男演员的名字...
actorRole:
label: 演员角色
placeholder: 他们扮演的角色...
director:
label: 导演
placeholder: 导演的名字...
writer:
label: 编剧
placeholder: 电影的编剧...
duration:
label: 时长
placeholder: 电影的长度(秒)...
releaseDate:
label: 发行日期
placeholder: 电影的发布日期...
tag:
label: 标签
placeholder: 与该电影相关的标签词...
videoOther:
title: 其他视频详情
videoTVShow:
title: 电视剧详情
website:
title: 常规信息
pageType:
label: 页面类型
placeholder: 选择您网站的类型...
pageTitle:
label: 标题
placeholder: 输入您网站的标题...
description:
label: 描述
placeholder: 输入您网站的描述...
url:
label: 页面 URL
placeholder: 输入您网站的 URL...
web: 网站
article: 文章
book: 书籍
profile: 个人资料
music:
label: 音乐
song: 歌曲
musicAlbum: 音乐专辑
playlist: 播放列表
radioStation: 电台
video:
label: 视频
movie: 电影
episode: 剧集
tvShow: 电视剧
otherVideo: 其他视频

View file

@ -6,7 +6,7 @@ import type { OGSchemaType, OGSchemaTypeElementSelect } from './OGSchemaType.typ
import TextareaCopyable from '@/components/TextareaCopyable.vue';
// Since type guards do not work in template
const { t } = useI18n();
const metadata = ref<{ type: string; [k: string]: any }>({
'type': 'website',
'twitter:card': 'summary_large_image',
@ -83,7 +83,7 @@ const metaTags = computed(() => {
</div>
</div>
<div>
<n-form-item label="Your meta tags">
<n-form-item :label="t('tools.og-meta-generator.yourMetaTags')">
<TextareaCopyable :value="metaTags" language="html" />
</n-form-item>
</div>

View file

@ -1,33 +1,44 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const article: OGSchemaType = {
name: 'Article',
name: t('tools.og-meta-generator.article.title'),
elements: [
{
type: 'input',
label: 'Publishing date',
label: t('tools.og-meta-generator.article.publishingDate.label'),
key: 'article:published_time',
placeholder: 'When the article was first published...',
placeholder: t('tools.og-meta-generator.article.publishingDate.placeholder'),
},
{
type: 'input',
label: 'Modification date',
label: t('tools.og-meta-generator.article.modificationDate.label'),
key: 'article:modified_time',
placeholder: 'When the article was last changed...',
placeholder: t('tools.og-meta-generator.article.modificationDate.placeholder'),
},
{
type: 'input',
label: 'Expiration date',
label: t('tools.og-meta-generator.article.expirationDate.label'),
key: 'article:expiration_time',
placeholder: 'When the article is out of date after...',
placeholder: t('tools.og-meta-generator.article.expirationDate.placeholder'),
},
{ type: 'input', label: 'Author', key: 'article:author', placeholder: 'Writers of the article...' },
{
type: 'input',
label: 'Section',
key: 'article:section',
placeholder: 'A high-level section name. E.g. Technology..',
label: t('tools.og-meta-generator.article.author.label'),
key: 'article:author',
placeholder: t('tools.og-meta-generator.article.author.placeholder'),
},
{
type: 'input',
label: t('tools.og-meta-generator.article.section.label'),
key: 'article:section',
placeholder: t('tools.og-meta-generator.article.section.placeholder'),
},
{
type: 'input',
label: t('tools.og-meta-generator.article.tag.label'),
key: 'article:tag',
placeholder: t('tools.og-meta-generator.article.tag.placeholder'),
},
{ type: 'input', label: 'Tag', key: 'article:tag', placeholder: 'Tag words associated with this article...' },
],
};

View file

@ -1,16 +1,17 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const book: OGSchemaType = {
name: 'Book',
name: t('tools.og-meta-generator.book.title'),
elements: [
{ type: 'input', label: 'Author', key: 'book:author', placeholder: 'Who wrote this book...' },
{ type: 'input', label: 'ISBN', key: 'book:isbn', placeholder: 'The International Standard Book Number...' },
{ type: 'input', label: t('tools.og-meta-generator.book.author.label'), key: 'book:author', placeholder: t('tools.og-meta-generator.book.author.placeholder') },
{ type: 'input', label: t('tools.og-meta-generator.book.ISBN.label'), key: 'book:isbn', placeholder: t('tools.og-meta-generator.book.ISBN.placeholder') },
{
type: 'input',
label: 'Release date',
label: t('tools.og-meta-generator.book.releaseDate.label'),
key: 'book:release_date',
placeholder: 'The date the book was released...',
placeholder: t('tools.og-meta-generator.book.releaseDate.placeholder'),
},
{ type: 'input', label: 'Tag', key: 'book:tag', placeholder: 'Tag words associated with this book...' },
{ type: 'input', label: t('tools.og-meta-generator.book.tag.label'), key: 'book:tag', placeholder: t('tools.og-meta-generator.book.tag.placeholder') },
],
};

View file

@ -1,30 +1,31 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const image: OGSchemaType = {
name: 'Image',
name: t('tools.og-meta-generator.image.title'),
elements: [
{
type: 'input',
label: 'Image url',
placeholder: 'The url of your website social image...',
label: t('tools.og-meta-generator.image.imageUrl.label'),
placeholder: t('tools.og-meta-generator.image.imageUrl.placeholder'),
key: 'image',
},
{
type: 'input',
label: 'Image alt',
placeholder: 'The alternative text of your website social image...',
label: t('tools.og-meta-generator.image.imageAlt.label'),
placeholder: t('tools.og-meta-generator.image.imageAlt.placeholder'),
key: 'image:alt',
},
{
type: 'input',
label: 'Width',
placeholder: 'Width in px of your website social image...',
label: t('tools.og-meta-generator.image.width.label'),
placeholder: t('tools.og-meta-generator.image.width.placeholder'),
key: 'image:width',
},
{
type: 'input',
label: 'Height',
placeholder: 'Height in px of your website social image...',
label: t('tools.og-meta-generator.image.height.label'),
placeholder: t('tools.og-meta-generator.image.height.placeholder'),
key: 'image:height',
},
],

View file

@ -1,27 +1,28 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const musicAlbum: OGSchemaType = {
name: 'Album details',
name: t('tools.og-meta-generator.albumDetails.title'),
elements: [
{ type: 'input', label: 'Song', key: 'music:song', placeholder: 'The song on this album...' },
{ type: 'input', label: t('tools.og-meta-generator.albumDetails.song.label'), key: 'music:song', placeholder: t('tools.og-meta-generator.albumDetails.song.placeholder') },
{
type: 'input',
label: 'Disc',
label: t('tools.og-meta-generator.albumDetails.disc.label'),
key: 'music:song:disc',
placeholder: 'The same as music:album:disc but in reverse...',
placeholder: t('tools.og-meta-generator.albumDetails.disc.placeholder'),
},
{
type: 'input',
label: 'Track',
label: t('tools.og-meta-generator.albumDetails.track.label'),
key: 'music:song:track',
placeholder: 'The same as music:album:track but in reverse...',
placeholder: t('tools.og-meta-generator.albumDetails.track.placeholder'),
},
{ type: 'input', label: 'Musician', key: 'music:musician', placeholder: 'The musician that made this song...' },
{ type: 'input', label: t('tools.og-meta-generator.albumDetails.musician.label'), key: 'music:musician', placeholder: t('tools.og-meta-generator.albumDetails.musician.placeholder') },
{
type: 'input',
label: 'Release date',
label: t('tools.og-meta-generator.albumDetails.releaseDate.label'),
key: 'music:release_date',
placeholder: 'The date the album was released...',
placeholder: t('tools.og-meta-generator.albumDetails.releaseDate.placeholder'),
},
],
};

View file

@ -1,21 +1,22 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const musicPlaylist: OGSchemaType = {
name: 'Playlist details',
name: t('tools.og-meta-generator.playlistDetails.title'),
elements: [
{ type: 'input', label: 'Song', key: 'music:song', placeholder: 'The song on this album...' },
{ type: 'input', label: t('tools.og-meta-generator.playlistDetails.song.label'), key: 'music:song', placeholder: t('tools.og-meta-generator.playlistDetails.song.placeholder') },
{
type: 'input',
label: 'Disc',
label: t('tools.og-meta-generator.playlistDetails.disc.label'),
key: 'music:song:disc',
placeholder: 'The same as music:album:disc but in reverse...',
placeholder: t('tools.og-meta-generator.playlistDetails.disc.placeholder'),
},
{
type: 'input',
label: 'Track',
label: t('tools.og-meta-generator.playlistDetails.track.label'),
key: 'music:song:track',
placeholder: 'The same as music:album:track but in reverse...',
placeholder: t('tools.og-meta-generator.playlistDetails.track.placeholder'),
},
{ type: 'input', label: 'Creator', key: 'music:creator', placeholder: 'The creator of this playlist...' },
{ type: 'input', label: t('tools.og-meta-generator.playlistDetails.creator.label'), key: 'music:creator', placeholder: t('tools.og-meta-generator.playlistDetails.creator.placeholder') },
],
};

View file

@ -1,8 +1,9 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const musicRadioStation: OGSchemaType = {
name: 'Radio station details',
name: t('tools.og-meta-generator.radioStationDetails.title'),
elements: [
{ type: 'input', label: 'Creator', key: 'music:creator', placeholder: 'The creator of this radio station...' },
{ type: 'input', label: t('tools.og-meta-generator.radioStationDetails.creator.label'), key: 'music:creator', placeholder: t('tools.og-meta-generator.radioStationDetails.creator.placeholder') },
],
};

View file

@ -1,21 +1,22 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const musicSong: OGSchemaType = {
name: 'Song details',
name: t('tools.og-meta-generator.songDetails.title'),
elements: [
{ type: 'input', label: 'Duration', placeholder: 'The duration of the song...', key: 'music:duration' },
{ type: 'input', label: 'Album', placeholder: 'The album this song is from...', key: 'music:album' },
{ type: 'input', label: t('tools.og-meta-generator.songDetails.duration.lebel'), placeholder: t('tools.og-meta-generator.songDetails.duration.placeholder'), key: 'music:duration' },
{ type: 'input', label: t('tools.og-meta-generator.songDetails.album.lebel'), placeholder: t('tools.og-meta-generator.songDetails.album.placeholder'), key: 'music:album' },
{
type: 'input',
label: 'Disc',
placeholder: 'Which disc of the album this song is on...',
label: t('tools.og-meta-generator.songDetails.disc.lebel'),
placeholder: t('tools.og-meta-generator.songDetails.disc.placeholder'),
key: 'music:album:disk',
},
{ type: 'input', label: 'Track', placeholder: ' Which track this song is...', key: 'music:album:track' },
{ type: 'input', label: t('tools.og-meta-generator.songDetails.track.lebel'), placeholder: t('tools.og-meta-generator.songDetails.track.placeholder'), key: 'music:album:track' },
{
type: 'input-multiple',
label: 'Musician',
placeholder: 'The musician that made this song...',
label: t('tools.og-meta-generator.songDetails.musician.lebel'),
placeholder: t('tools.og-meta-generator.songDetails.musician.placeholder'),
key: 'music:musician',
},
],

View file

@ -1,21 +1,22 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const profile: OGSchemaType = {
name: 'Profile',
name: t('tools.og-meta-generator.profile.title'),
elements: [
{
type: 'input',
label: 'First name',
placeholder: 'Enter the first name of the person...',
label: t('tools.og-meta-generator.profile.firstName.lebel'),
placeholder: t('tools.og-meta-generator.profile.firstName.placeholder'),
key: 'profile:first_name',
},
{
type: 'input',
label: 'Last name',
placeholder: 'Enter the last name of the person...',
label: t('tools.og-meta-generator.profile.lastName.lebel'),
placeholder: t('tools.og-meta-generator.profile.lastName.placeholder'),
key: 'profile:last_name',
},
{ type: 'input', label: 'Username', placeholder: 'Enter the username of the person...', key: 'profile:username' },
{ type: 'input', label: 'Gender', placeholder: 'Enter the gender of the person...', key: 'profile:gender' },
{ type: 'input', label: t('tools.og-meta-generator.profile.username.lebel'), placeholder: t('tools.og-meta-generator.profile.username.placeholder'), key: 'profile:username' },
{ type: 'input', label: t('tools.og-meta-generator.profile.gender.lebel'), placeholder: t('tools.og-meta-generator.profile.gender.placeholder'), key: 'profile:gender' },
],
};

View file

@ -1,30 +1,31 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const twitter: OGSchemaType = {
name: 'Twitter',
name: t('tools.og-meta-generator.twitter.title'),
elements: [
{
type: 'select',
options: [
{ label: 'Summary', value: 'summary' },
{ label: 'Summary with large image', value: 'summary_large_image' },
{ label: 'Application', value: 'app' },
{ label: 'Player', value: 'player' },
{ label: t('tools.og-meta-generator.twitter.card.summary'), value: 'summary' },
{ label: t('tools.og-meta-generator.twitter.card.summaryWithLargeImage'), value: 'summary_large_image' },
{ label: t('tools.og-meta-generator.twitter.card.application'), value: 'app' },
{ label: t('tools.og-meta-generator.twitter.card.player'), value: 'player' },
],
label: 'Card type',
placeholder: 'The Twitter card type...',
label: t('tools.og-meta-generator.twitter.card.label'),
placeholder: t('tools.og-meta-generator.twitter.card.placeholder'),
key: 'twitter:card',
},
{
type: 'input',
label: 'Site account',
placeholder: 'The name of the Twitter account of the site (ex: @ittoolsdottech)...',
label: t('tools.og-meta-generator.twitter.site.label'),
placeholder: t('tools.og-meta-generator.twitter.site.placeholder'),
key: 'twitter:site',
},
{
type: 'input',
label: 'Creator acc.',
placeholder: 'The name of the Twitter account of the creator (ex: @cthmsst)...',
label: t('tools.og-meta-generator.twitter.creator.label'),
placeholder: t('tools.og-meta-generator.twitter.creator.placeholder'),
key: 'twitter:creator',
},
],

View file

@ -1,10 +1,11 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { videoMovie } from './videoMovie';
import { translate as t } from '@/plugins/i18n.plugin';
export const videoEpisode: OGSchemaType = {
name: 'Video episode details',
name: t('tools.og-meta-generator.videoEpisode.title'),
elements: [
...videoMovie.elements,
{ type: 'input', label: 'Series', key: 'video:series', placeholder: 'Which series this episode belongs to...' },
{ type: 'input', label: t('tools.og-meta-generator.videoEpisode.series.label'), key: 'video:series', placeholder: t('tools.og-meta-generator.videoEpisode.series.placeholder') },
],
};

View file

@ -1,29 +1,30 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
export const videoMovie: OGSchemaType = {
name: 'Movie details',
name: t('tools.og-meta-generator.videoMovie.title'),
elements: [
{
type: 'input-multiple',
label: 'Actor',
label: t('tools.og-meta-generator.videoMovie.actor.label'),
key: 'video:actor',
placeholder: 'Name of the actress/actor...',
placeholder: t('tools.og-meta-generator.videoMovie.actor.placeholder'),
},
// { type: 'input', label: 'Actor role', key: 'video:actor:role', placeholder: 'The role they played...' },
// { type: 'input', label: t('tools.og-meta-generator.videoMovie.actorRole.label'), key: 'video:actor:role', placeholder: t('tools.og-meta-generator.videoMovie.actorRole.placeholder') },
{
type: 'input-multiple',
label: 'Director',
label: t('tools.og-meta-generator.videoMovie.director.label'),
key: 'video:director',
placeholder: 'Name of the director...',
placeholder: t('tools.og-meta-generator.videoMovie.director.placeholder'),
},
{ type: 'input-multiple', label: 'Writer', key: 'video:writer', placeholder: 'Writers of the movie...' },
{ type: 'input', label: 'Duration', key: 'video:duration', placeholder: 'The movie\'s length in seconds...' },
{ type: 'input-multiple', label: t('tools.og-meta-generator.videoMovie.writer.label'), key: 'video:writer', placeholder: t('tools.og-meta-generator.videoMovie.writer.placeholder') },
{ type: 'input', label: t('tools.og-meta-generator.videoMovie.duration.label'), key: 'video:duration', placeholder: t('tools.og-meta-generator.videoMovie.duration.placeholder') },
{
type: 'input',
label: 'Release date',
label: t('tools.og-meta-generator.videoMovie.releaseDate.label'),
key: 'video:release_date',
placeholder: 'The date the movie was released...',
placeholder: t('tools.og-meta-generator.videoMovie.releaseDate.placeholder'),
},
{ type: 'input', label: 'Tag', key: 'video:tag', placeholder: 'Tag words associated with this movie...' },
{ type: 'input', label: t('tools.og-meta-generator.videoMovie.tag.label'), key: 'video:tag', placeholder: t('tools.og-meta-generator.videoMovie.tag.placeholder') },
],
};

View file

@ -1,7 +1,8 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { videoMovie } from './videoMovie';
import { translate as t } from '@/plugins/i18n.plugin';
export const videoOther: OGSchemaType = {
name: 'Other video details',
name: t('tools.og-meta-generator.videoOther.title'),
elements: [...videoMovie.elements],
};

View file

@ -1,7 +1,8 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { videoMovie } from './videoMovie';
import { translate as t } from '@/plugins/i18n.plugin';
export const videoTVShow: OGSchemaType = {
name: 'TV show details',
name: t('tools.og-meta-generator.videoTVShow.title'),
elements: [...videoMovie.elements],
};

View file

@ -1,55 +1,56 @@
import type { OGSchemaType } from '../OGSchemaType.type';
import { translate as t } from '@/plugins/i18n.plugin';
const typeOptions = [
{ label: 'Website', value: 'website' },
{ label: 'Article', value: 'article' },
{ label: 'Book', value: 'book' },
{ label: 'Profile', value: 'profile' },
{ label: t('tools.og-meta-generator.website.web'), value: 'website' },
{ label: t('tools.og-meta-generator.website.article'), value: 'article' },
{ label: t('tools.og-meta-generator.website.book'), value: 'book' },
{ label: t('tools.og-meta-generator.website.profile'), value: 'profile' },
{
type: 'group',
label: 'Music',
label: t('tools.og-meta-generator.website.music.label'),
key: 'Music',
children: [
{ label: 'Song', value: 'music.song' },
{ label: 'Music album', value: 'music.album' },
{ label: 'Playlist', value: 'music.playlist' },
{ label: 'Radio station', value: 'music.radio_station' },
{ label: t('tools.og-meta-generator.website.music.song'), value: 'music.song' },
{ label: t('tools.og-meta-generator.website.music.musicAlbum'), value: 'music.album' },
{ label: t('tools.og-meta-generator.website.music.playlist'), value: 'music.playlist' },
{ label: t('tools.og-meta-generator.website.music.radioStation'), value: 'music.radio_station' },
],
},
{
type: 'group',
label: 'Video',
label: t('tools.og-meta-generator.website.video.label'),
key: 'Video',
children: [
{ label: 'Movie', value: 'video.movie' },
{ label: 'Episode', value: 'video.episode' },
{ label: 'TV show', value: 'video.tv_show' },
{ label: 'Other video', value: 'video.other' },
{ label: t('tools.og-meta-generator.website.video.movie'), value: 'video.movie' },
{ label: t('tools.og-meta-generator.website.video.episode'), value: 'video.episode' },
{ label: t('tools.og-meta-generator.website.video.tvShow'), value: 'video.tv_show' },
{ label: t('tools.og-meta-generator.website.video.otherVideo'), value: 'video.other' },
],
},
];
export const website: OGSchemaType = {
name: 'General information',
name: t('tools.og-meta-generator.website.title'),
elements: [
{
type: 'select',
label: 'Page type',
placeholder: 'Select the type of your website...',
label: t('tools.og-meta-generator.website.pageType.label'),
placeholder: t('tools.og-meta-generator.website.pageType.placeholder'),
key: 'type',
options: typeOptions,
},
{ type: 'input', label: 'Title', placeholder: 'Enter the title of your website...', key: 'title' },
{ type: 'input', label: t('tools.og-meta-generator.website.pageTitle.label'), placeholder: t('tools.og-meta-generator.website.pageTitle.placeholder'), key: 'title' },
{
type: 'input',
label: 'Description',
placeholder: 'Enter the description of your website...',
label: t('tools.og-meta-generator.website.description.label'),
placeholder: t('tools.og-meta-generator.website.description.placeholder'),
key: 'description',
},
{
type: 'input',
label: 'Page URL',
placeholder: 'Enter the url of your website...',
label: t('tools.og-meta-generator.website.url.label'),
placeholder: t('tools.og-meta-generator.website.url.placeholder'),
key: 'url',
},
],

View file

@ -1,10 +1,11 @@
import { World } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Mime types',
name: t('tools.mime-types.title'),
path: '/mime-types',
description: 'Convert mime types to extensions and vice-versa.',
description: t('tools.mime-types.description'),
keywords: ['mime', 'types', 'extension', 'content', 'type'],
component: () => import('./mime-types.vue'),
icon: World,

View file

@ -0,0 +1,23 @@
tools:
mime-types:
title: Mime types
description: Convert mime types to extensions and vice-versa.
mimeType:
title: Mime type to extension
description: Know which file extensions are associated to a mime-type
placeholder: 'Select your mimetype here... (ex: application/pdf)'
extensionsFound:
before: 'Extensions of files with the '
after: ' mime-type:'
extension:
title: File extension to mime type
description: Know which mime type is associated to a file extension
placeholder: 'Select your mimetype here... (ex: application/pdf)'
selectedExtension:
before: 'Mime type associated to the extension '
after: ' file'
ext: 'extension:'
table:
mimeTypes: Mime types
extensions: Extensions

View file

@ -0,0 +1,23 @@
tools:
mime-types:
title: MIME 类型
description: 将 MIME 类型转换为扩展名,反之亦然。
mimeType:
title: MIME 类型转扩展名
description: 知道哪些文件扩展名与特定 MIME 类型相关联
placeholder: '在此处选择您的 MIME 类型...例如application/pdf'
extensionsFound:
before: '具有'
after: ' MIME 类型的文件扩展名:'
extension:
title: 文件扩展名转 MIME 类型
description: 知道哪种 MIME 类型与文件扩展名相关联
placeholder: '在此处选择您的 MIME 类型...例如application/pdf'
selectedExtension:
before: '与扩展名'
after: '相关联的 MIME 类型:'
ext: '扩展名:'
table:
mimeTypes: MIME 类型
extensions: 扩展名

View file

@ -1,6 +1,8 @@
<script setup lang="ts">
import { types as extensionToMimeType, extensions as mimeTypeToExtension } from 'mime-types';
const { t } = useI18n();
const mimeInfos = Object.entries(mimeTypeToExtension).map(([mimeType, extensions]) => ({ mimeType, extensions }));
const mimeToExtensionsOptions = Object.keys(mimeTypeToExtension).map(label => ({ label, value: label }));
@ -21,23 +23,23 @@ const mimeTypeFound = computed(() => (selectedExtension.value ? extensionToMimeT
<template>
<c-card>
<n-h2 style="margin-bottom: 0">
Mime type to extension
{{ t('tools.mime-types.mimeType.title') }}
</n-h2>
<div style="opacity: 0.8">
Know which file extensions are associated to a mime-type
{{ t('tools.mime-types.mimeType.description') }}
</div>
<c-select
v-model:value="selectedMimeType"
searchable
my-4
:options="mimeToExtensionsOptions"
placeholder="Select your mimetype here... (ex: application/pdf)"
:placeholder="t('tools.mime-types.mimeType.placeholder')"
/>
<div v-if="extensionsFound.length > 0">
Extensions of files with the <n-tag round :bordered="false">
{{ t('tools.mime-types.mimeType.extensionsFound.before') }}<n-tag round :bordered="false">
{{ selectedMimeType }}
</n-tag> mime-type:
</n-tag>{{ t('tools.mime-types.mimeType.extensionsFound.after') }}
<div style="margin-top: 10px">
<n-tag
v-for="extension of extensionsFound"
@ -55,24 +57,24 @@ const mimeTypeFound = computed(() => (selectedExtension.value ? extensionToMimeT
<c-card>
<n-h2 style="margin-bottom: 0">
File extension to mime type
{{ t('tools.mime-types.extension.title') }}
</n-h2>
<div style="opacity: 0.8">
Know which mime type is associated to a file extension
{{ t('tools.mime-types.extension.description') }}
</div>
<c-select
v-model:value="selectedExtension"
searchable
my-4
:options="extensionToMimeTypeOptions"
placeholder="Select your mimetype here... (ex: application/pdf)"
:placeholder="t('tools.mime-types.extension.placeholder')"
/>
<div v-if="selectedExtension">
Mime type associated to the extension <n-tag round :bordered="false">
{{ t('tools.mime-types.extension.selectedExtension.before') }}<n-tag round :bordered="false">
{{ selectedExtension }}
</n-tag> file
extension:
</n-tag>{{ t('tools.mime-types.extension.selectedExtension.after') }}
{{ t('tools.mime-types.extension.selectedExtension.ext') }}
<div style="margin-top: 10px">
<n-tag round :bordered="false" type="primary" style="margin-right: 10px">
{{ mimeTypeFound }}
@ -85,8 +87,8 @@ const mimeTypeFound = computed(() => (selectedExtension.value ? extensionToMimeT
<n-table>
<thead>
<tr>
<th>Mime types</th>
<th>Extensions</th>
<th>{{ t('tools.mime-types.table.mimeTypes') }}</th>
<th>{{ t('tools.mime-types.table.extensions') }}</th>
</tr>
</thead>
<tbody>

View file

@ -1,10 +1,11 @@
import { DeviceMobile } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'OTP code generator',
name: t('tools.otp-generator.title'),
path: '/otp-generator',
description: 'Generate and validate time-based OTP (one time password) for multi-factor authentication.',
description: t('tools.otp-generator.description'),
keywords: [
'otp',
'code',

View file

@ -0,0 +1,30 @@
tools:
otp-generator:
title: OTP code generator
description: Generate and validate time-based OTP (one time password) for multi-factor authentication.
secretLabel: Secret
secretPlaceholder: Paste your TOTP secret...
generate: Generate a new random secret
timeLeft: Next in {second}s
keyUri: Open Key URI in new tab
hexadecimalLabel: Secret in hexadecimal
hexadecimalPlaceholder: Secret in hex will be displayed here
epochLabel: Epoch
epochPlaceholder: Epoch in sec will be displayed here
iteration: Iteration
countLabel: 'Count:'
countPlaceholder: Iteration count will be displayed here
paddedHexLabel: 'Padded hex:'
paddedHexPlaceholder: Iteration count in hex will be displayed here
previous: Previous
currentOTP: Current OTP
next: Next
copied: Copied !
copyPreviousOTP: Copy previous OTP
copyCurrentOTP: Copy current OTP
copyNextOTP: Copy next OTP
typeErrorMessage: Secret should be a base32 string
invalidMessage: Please set a secret

View file

@ -0,0 +1,30 @@
tools:
otp-generator:
title: OTP 验证码生成器
description: 生成和验证基于时间的 OTP一次性密码以进行多因素身份验证。
secretLabel: 密钥
secretPlaceholder: 粘贴您的 TOTP 密钥...
generate: 生成一个新的随机密钥
timeLeft: 还剩 {second} 秒
keyUri: 在新标签页中打开 Key URI
hexadecimalLabel: 十六进制密钥
hexadecimalPlaceholder: 十六进制密钥将显示在此处
epochLabel: 历元
epochPlaceholder: 秒数将显示在此处
iteration: 迭代
countLabel: '计数:'
countPlaceholder: 迭代计数将显示在此处
paddedHexLabel: '填充的十六进制:'
paddedHexPlaceholder: 十六进制迭代计数将显示在此处
previous: 上一个
currentOTP: 当前 OTP
next: 下一个
copied: 已复制!
copyPreviousOTP: 复制上一个 OTP
copyCurrentOTP: 复制当前 OTP
copyNextOTP: 复制下一个 OTP
typeErrorMessage: 密钥应为 base32 字符串
invalidMessage: 请设置一个密钥

View file

@ -8,6 +8,7 @@ import { useStyleStore } from '@/stores/style.store';
import InputCopyable from '@/components/InputCopyable.vue';
import { computedRefreshable } from '@/composable/computedRefreshable';
const { t } = useI18n();
const now = useTimestamp();
const interval = computed(() => (now.value / 1000) % 30);
const theme = useThemeVars();
@ -41,11 +42,11 @@ const { qrcode } = useQRCode({
const secretValidationRules = [
{
message: 'Secret should be a base32 string',
message: t('tools.otp-generator.typeErrorMessage'),
validator: (value: string) => value.toUpperCase().match(/^[A-Z234567]+$/),
},
{
message: 'Please set a secret',
message: t('tools.otp-generator.invalidMessage'),
validator: (value: string) => value !== '',
},
];
@ -55,13 +56,13 @@ const secretValidationRules = [
<div style="max-width: 350px">
<c-input-text
v-model:value="secret"
label="Secret"
placeholder="Paste your TOTP secret..."
:label="t('tools.otp-generator.secretLabel')"
:placeholder="t('tools.otp-generator.secretPlaceholder')"
mb-5
:validation-rules="secretValidationRules"
>
<template #suffix>
<c-tooltip tooltip="Generate a new random secret">
<c-tooltip :tooltip="t('tools.otp-generator.generate')">
<c-button circle variant="text" size="small" @click="refreshSecret">
<icon-mdi-refresh />
</c-button>
@ -74,53 +75,49 @@ const secretValidationRules = [
<n-progress :percentage="(100 * interval) / 30" :color="theme.primaryColor" :show-indicator="false" />
<div style="text-align: center">
Next in {{ String(Math.floor(30 - interval)).padStart(2, '0') }}s
{{ t('tools.otp-generator.timeLeft', { second: String(Math.floor(30 - interval)).padStart(2, '0') }) }}
</div>
</div>
<div mt-4 flex flex-col items-center justify-center gap-3>
<n-image :src="qrcode" />
<c-button :href="keyUri" target="_blank">
Open Key URI in new tab
{{ t('tools.otp-generator.keyUri') }}
</c-button>
</div>
</div>
<div style="max-width: 350px">
<InputCopyable
label="Secret in hexadecimal"
:label="t('tools.otp-generator.hexadecimalLabel')"
:value="base32toHex(secret)"
readonly
placeholder="Secret in hex will be displayed here"
:placeholder="t('tools.otp-generator.hexadecimalPlaceholder')"
mb-5
/>
<InputCopyable
label="Epoch"
:label="t('tools.otp-generator.epochLabel')"
:value="Math.floor(now / 1000).toString()"
readonly
mb-5
placeholder="Epoch in sec will be displayed here"
:placeholder="t('tools.otp-generator.epochPlaceholder')"
/>
<p>Iteration</p>
<p>{{ t('tools.otp-generator.iteration') }}</p>
<InputCopyable
:value="String(getCounterFromTime({ now, timeStep: 30 }))"
readonly
label="Count:"
label-position="left"
label-width="90px"
label-align="right"
placeholder="Iteration count will be displayed here"
:label="t('tools.otp-generator.countLabel')"
:placeholder="t('tools.otp-generator.countPlaceholder')"
mb-5
/>
<InputCopyable
:value="getCounterFromTime({ now, timeStep: 30 }).toString(16).padStart(16, '0')"
readonly
placeholder="Iteration count in hex will be displayed here"
label-position="left"
label-width="90px"
label-align="right"
label="Padded hex:"
:placeholder="t('tools.otp-generator.paddedHexPlaceholder')"
:label="t('tools.otp-generator.paddedHexLabel')"
mb-5
/>
</div>
</template>

View file

@ -6,6 +6,7 @@ const { copy: copyPrevious, isJustCopied: previousCopied } = useCopy({ createToa
const { copy: copyCurrent, isJustCopied: currentCopied } = useCopy({ createToast: false });
const { copy: copyNext, isJustCopied: nextCopied } = useCopy({ createToast: false });
const { t } = useI18n();
const { tokens } = toRefs(props);
</script>
@ -13,29 +14,29 @@ const { tokens } = toRefs(props);
<div>
<div mb-5px w-full flex items-center>
<div flex-1 text-left>
Previous
{{ t('tools.otp-generator.previous') }}
</div>
<div flex-1 text-center>
Current OTP
{{ t('tools.otp-generator.currentOTP') }}
</div>
<div flex-1 text-right>
Next
</div>
</div>
<div flex items-center>
<c-tooltip :tooltip="previousCopied ? 'Copied !' : 'Copy previous OTP'" position="bottom" flex-1>
<c-tooltip :tooltip="previousCopied ? t('tools.otp-generator.copied') : t('tools.otp-generator.copyPreviousOTP')" position="bottom" flex-1>
<c-button data-test-id="previous-otp" w-full important:h-12 important:rounded-r-none important:font-mono @click.prevent="copyPrevious(tokens.previous)">
{{ tokens.previous }}
</c-button>
</c-tooltip>
<c-tooltip :tooltip="currentCopied ? 'Copied !' : 'Copy current OTP'" position="bottom" flex-1 flex-basis-5xl>
<c-tooltip :tooltip="currentCopied ? t('tools.otp-generator.copied') : t('tools.otp-generator.copyCurrentOTP')" position="bottom" flex-1 flex-basis-5xl>
<c-button
data-test-id="current-otp" w-full important:border-x="1px solid gray op-40" important:h-12 important:rounded-0 important:text-22px @click.prevent="copyCurrent(tokens.current)"
>
{{ tokens.current }}
</c-button>
</c-tooltip>
<c-tooltip :tooltip="nextCopied ? 'Copied !' : 'Copy next OTP'" position="bottom" flex-1>
<c-tooltip :tooltip="nextCopied ? t('tools.otp-generator.copied') : t('tools.otp-generator.copyNextOTP')" position="bottom" flex-1>
<c-button data-test-id="next-otp" w-full important:h-12 important:rounded-l-none @click.prevent="copyNext(tokens.next)">
{{ tokens.next }}
</c-button>

View file

@ -1,11 +1,11 @@
import { defineTool } from '../tool';
import PasswordIcon from '~icons/mdi/form-textbox-password';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.password-strength-analyser.title'),
name: t('tools.password-strength-analyser.title'),
path: '/password-strength-analyser',
description: translate('tools.password-strength-analyser.description'),
description: t('tools.password-strength-analyser.description'),
keywords: ['password', 'strength', 'analyser', 'and', 'crack', 'time', 'estimation', 'brute', 'force', 'attack', 'entropy', 'cracking', 'hash', 'hashing', 'algorithm', 'algorithms', 'md5', 'sha1', 'sha256', 'sha512', 'bcrypt', 'scrypt', 'argon2', 'argon2id', 'argon2i', 'argon2d'],
component: () => import('./password-strength-analyser.vue'),
icon: PasswordIcon,

View file

@ -1,5 +1,5 @@
import _ from 'lodash';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export { getPasswordCrackTimeEstimation, getCharsetLength };
@ -12,24 +12,24 @@ function prettifyExponentialNotation(exponentialNotation: number) {
function getHumanFriendlyDuration({ seconds }: { seconds: number }) {
if (seconds <= 0.001) {
return translate('tools.password-strength-analyser.instantly');
return t('tools.password-strength-analyser.instantly');
}
if (seconds <= 1) {
return translate('tools.password-strength-analyser.lessThanASecond');
return t('tools.password-strength-analyser.lessThanASecond');
}
const timeUnits = [
{ unit: translate('tools.password-strength-analyser.millenium'), secondsInUnit: 31536000000, format: prettifyExponentialNotation, plural: translate('tools.password-strength-analyser.millennia') },
{ unit: translate('tools.password-strength-analyser.century'), secondsInUnit: 3153600000, plural: translate('tools.password-strength-analyser.centuries') },
{ unit: translate('tools.password-strength-analyser.decade'), secondsInUnit: 315360000, plural: translate('tools.password-strength-analyser.decades') },
{ unit: translate('tools.password-strength-analyser.year'), secondsInUnit: 31536000, plural: translate('tools.password-strength-analyser.years') },
{ unit: translate('tools.password-strength-analyser.month'), secondsInUnit: 2592000, plural: translate('tools.password-strength-analyser.months') },
{ unit: translate('tools.password-strength-analyser.week'), secondsInUnit: 604800, plural: translate('tools.password-strength-analyser.weeks') },
{ unit: translate('tools.password-strength-analyser.day'), secondsInUnit: 86400, plural: translate('tools.password-strength-analyser.days') },
{ unit: translate('tools.password-strength-analyser.hour'), secondsInUnit: 3600, plural: translate('tools.password-strength-analyser.hours') },
{ unit: translate('tools.password-strength-analyser.minute'), secondsInUnit: 60, plural: translate('tools.password-strength-analyser.minutes') },
{ unit: translate('tools.password-strength-analyser.second'), secondsInUnit: 1, plural: translate('tools.password-strength-analyser.seconds') },
{ unit: t('tools.password-strength-analyser.millenium'), secondsInUnit: 31536000000, format: prettifyExponentialNotation, plural: t('tools.password-strength-analyser.millennia') },
{ unit: t('tools.password-strength-analyser.century'), secondsInUnit: 3153600000, plural: t('tools.password-strength-analyser.centuries') },
{ unit: t('tools.password-strength-analyser.decade'), secondsInUnit: 315360000, plural: t('tools.password-strength-analyser.decades') },
{ unit: t('tools.password-strength-analyser.year'), secondsInUnit: 31536000, plural: t('tools.password-strength-analyser.years') },
{ unit: t('tools.password-strength-analyser.month'), secondsInUnit: 2592000, plural: t('tools.password-strength-analyser.months') },
{ unit: t('tools.password-strength-analyser.week'), secondsInUnit: 604800, plural: t('tools.password-strength-analyser.weeks') },
{ unit: t('tools.password-strength-analyser.day'), secondsInUnit: 86400, plural: t('tools.password-strength-analyser.days') },
{ unit: t('tools.password-strength-analyser.hour'), secondsInUnit: 3600, plural: t('tools.password-strength-analyser.hours') },
{ unit: t('tools.password-strength-analyser.minute'), secondsInUnit: 60, plural: t('tools.password-strength-analyser.minutes') },
{ unit: t('tools.password-strength-analyser.second'), secondsInUnit: 1, plural: t('tools.password-strength-analyser.seconds') },
];
return _.chain(timeUnits)

View file

@ -1,11 +1,11 @@
import { defineTool } from '../tool';
import FileCertIcon from '~icons/mdi/file-certificate-outline';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.pdf-signature-checker.title'),
name: t('tools.pdf-signature-checker.title'),
path: '/pdf-signature-checker',
description: translate('tools.pdf-signature-checker.description'),
description: t('tools.pdf-signature-checker.description'),
keywords: ['pdf', 'signature', 'checker', 'verify', 'validate', 'sign'],
component: () => import('./pdf-signature-checker.vue'),
icon: FileCertIcon,

View file

@ -1,11 +1,11 @@
import { LetterX } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.roman-numeral-converter.title'),
name: t('tools.roman-numeral-converter.title'),
path: '/roman-numeral-converter',
description: translate('tools.roman-numeral-converter.description'),
description: t('tools.roman-numeral-converter.description'),
keywords: ['roman', 'arabic', 'converter', 'X', 'I', 'V', 'L', 'C', 'D', 'M'],
component: () => import('./roman-numeral-converter.vue'),
icon: LetterX,

View file

@ -1,11 +1,11 @@
import { Certificate } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.rsa-key-pair-generator.title'),
name: t('tools.rsa-key-pair-generator.title'),
path: '/rsa-key-pair-generator',
description: translate('tools.rsa-key-pair-generator.description'),
description: t('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,

View file

@ -0,0 +1,4 @@
tools:
slugify-string:
title: Slugify string
description: Make a string url, filename and id safe.

View file

@ -1,11 +1,11 @@
import { Binary } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.text-to-binary.title'),
name: t('tools.text-to-binary.title'),
path: '/text-to-binary',
description: translate('tools.text-to-binary.description'),
description: t('tools.text-to-binary.description'),
keywords: ['text', 'to', 'binary', 'converter', 'encode', 'decode', 'ascii'],
component: () => import('./text-to-binary.vue'),
icon: Binary,

View file

@ -1,4 +1,4 @@
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export { convertTextToAsciiBinary, convertAsciiBinaryToText };
@ -13,7 +13,7 @@ function convertAsciiBinaryToText(binary: string): string {
const cleanBinary = binary.replace(/[^01]/g, '');
if (cleanBinary.length % 8) {
throw new Error(translate('tools.text-to-binary.invalidBinaryString'));
throw new Error(t('tools.text-to-binary.invalidBinaryString'));
}
return cleanBinary

View file

@ -1,11 +1,11 @@
import { Speakerphone } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.text-to-nato-alphabet.title'),
name: t('tools.text-to-nato-alphabet.title'),
path: '/text-to-nato-alphabet',
description: translate('tools.text-to-nato-alphabet.description'),
description: t('tools.text-to-nato-alphabet.description'),
keywords: ['string', 'nato', 'alphabet', 'phonetic', 'oral', 'transmission'],
component: () => import('./text-to-nato-alphabet.vue'),
icon: Speakerphone,

View file

@ -1,11 +1,11 @@
import { ArrowsShuffle } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.token-generator.title'),
name: t('tools.token-generator.title'),
path: '/token-generator',
description: translate('token-generator.description'),
description: t('tools.token-generator.description'),
keywords: [
'token',
'random',

View file

@ -1,12 +1,12 @@
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
import BracketIcon from '~icons/mdi/code-brackets';
export const tool = defineTool({
name: translate('tools.toml-to-json.title'),
name: t('tools.toml-to-json.title'),
path: '/toml-to-json',
description: translate('tools.toml-to-json.description'),
description: t('tools.toml-to-json.description'),
keywords: ['toml', 'json', 'convert', 'online', 'transform', 'parser'],
component: () => import('./toml-to-json.vue'),
icon: BracketIcon,

View file

@ -1,12 +1,12 @@
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
import BracketIcon from '~icons/mdi/code-brackets';
export const tool = defineTool({
name: translate('tools.toml-to-yaml.title'),
name: t('tools.toml-to-yaml.title'),
path: '/toml-to-yaml',
description: translate('tools.toml-to-yaml.description'),
description: t('tools.toml-to-yaml.description'),
keywords: ['toml', 'yaml', 'convert', 'online', 'transform', 'parse'],
component: () => import('./toml-to-yaml.vue'),
icon: BracketIcon,

View file

@ -1,11 +1,11 @@
import { SortDescendingNumbers } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.ulid-generator.title'),
name: t('tools.ulid-generator.title'),
path: '/ulid-generator',
description: translate('tools.ulid-generator.description'),
description: t('tools.ulid-generator.description'),
keywords: ['ulid', 'generator', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
component: () => import('./ulid-generator.vue'),
icon: SortDescendingNumbers,

View file

@ -1,10 +1,11 @@
import { Link } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Encode/decode url formatted strings',
name: t('tools.url-encoder.title'),
path: '/url-encoder',
description: 'Encode to url-encoded format (also known as "percent-encoded") or decode from it.',
description: t('tools.url-encoder.description'),
keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'],
component: () => import('./url-encoder.vue'),
icon: Link,

View file

@ -0,0 +1,23 @@
tools:
url-encoder:
title: Encode/decode url formatted strings
description: Encode to url-encoded format (also known as "percent-encoded") or decode from it.
encode:
title: Encode
inputLabel: 'Your string :'
inputPlaceholder: The string to encode
outputLabel: 'Your string encoded :'
outputPlaceholder: Your string encoded
copied: Encoded string copied to the clipboard
inValidMessage: Impossible to parse this string
decode:
title: Decode
inputLabel: 'Your encoded string :'
inputPlaceholder: The string to decode
outputLabel: 'Your string decoded :'
outputPlaceholder: Your string decoded
copied: Decoded string copied to the clipboard
inValidMessage: Impossible to parse this string
copy: Copy

View file

@ -0,0 +1,23 @@
tools:
url-encoder:
title: 编码/解码 URL 格式化字符串
description: 编码为 URL 编码格式(也称为 "百分比编码")或从中解码。
encode:
title: 编码
inputLabel: '您的字符串:'
inputPlaceholder: 要编码的字符串
outputLabel: '您的字符串编码:'
outputPlaceholder: 您的字符串编码
copied: 编码字符串已复制到剪贴板
inValidMessage: 无法解析此字符串
decode:
title: 解码
inputLabel: '您的编码字符串:'
inputPlaceholder: 要解码的字符串
outputLabel: '您的字符串解码:'
outputPlaceholder: 您的字符串解码
copied: 解码字符串已复制到剪贴板
inValidMessage: 无法解析此字符串
copy: 复制

View file

@ -4,6 +4,8 @@ import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { withDefaultOnError } from '@/utils/defaults';
const { t } = useI18n();
const encodeInput = ref('Hello world :)');
const encodeOutput = computed(() => withDefaultOnError(() => encodeURIComponent(encodeInput.value), ''));
@ -12,12 +14,12 @@ const encodedValidation = useValidation({
rules: [
{
validator: value => isNotThrowing(() => encodeURIComponent(value)),
message: 'Impossible to parse this string',
message: t('tools.url-encoder.encode.inValidMessage'),
},
],
});
const { copy: copyEncoded } = useCopy({ source: encodeOutput, text: 'Encoded string copied to the clipboard' });
const { copy: copyEncoded } = useCopy({ source: encodeOutput, text: t('tools.url-encoder.encode.copied') });
const decodeInput = ref('Hello%20world%20%3A)');
const decodeOutput = computed(() => withDefaultOnError(() => decodeURIComponent(decodeInput.value), ''));
@ -27,70 +29,70 @@ const decodeValidation = useValidation({
rules: [
{
validator: value => isNotThrowing(() => decodeURIComponent(value)),
message: 'Impossible to parse this string',
message: t('tools.url-encoder.decode.inValidMessage'),
},
],
});
const { copy: copyDecoded } = useCopy({ source: decodeOutput, text: 'Decoded string copied to the clipboard' });
const { copy: copyDecoded } = useCopy({ source: decodeOutput, text: t('tools.url-encoder.decode.copied') });
</script>
<template>
<c-card title="Encode">
<c-card :title="t('tools.url-encoder.encode.title')">
<c-input-text
v-model:value="encodeInput"
label="Your string :"
:label="t('tools.url-encoder.encode.inputLabel')"
:validation="encodedValidation"
multiline
autosize
placeholder="The string to encode"
:placeholder="t('tools.url-encoder.encode.inputPlaceholder')"
rows="2"
mb-3
/>
<c-input-text
label="Your string encoded :"
:label="t('tools.url-encoder.encode.outputLabel')"
:value="encodeOutput"
multiline
autosize
readonly
placeholder="Your string encoded"
:placeholder="t('tools.url-encoder.encode.outputPlaceholder')"
rows="2"
mb-3
/>
<div flex justify-center>
<c-button @click="copyEncoded()">
Copy
{{ t('tools.url-encoder.copy') }}
</c-button>
</div>
</c-card>
<c-card title="Decode">
<c-card :title="t('tools.url-encoder.decode.title')">
<c-input-text
v-model:value="decodeInput"
label="Your encoded string :"
:label="t('tools.url-encoder.decode.inputLabel')"
:validation="decodeValidation"
multiline
autosize
placeholder="The string to decode"
:placeholder="t('tools.url-encoder.decode.inputPlaceholder')"
rows="2"
mb-3
/>
<c-input-text
label="Your string decoded :"
:label="t('tools.url-encoder.decode.outputLabel')"
:value="decodeOutput"
multiline
autosize
readonly
placeholder="Your string decoded"
:placeholder="t('tools.url-encoder.decode.outputPlaceholder')"
rows="2"
mb-3
/>
<div flex justify-center>
<c-button @click="copyDecoded()">
Copy
{{ t('tools.url-encoder.copy') }}
</c-button>
</div>
</c-card>

View file

@ -1,11 +1,11 @@
import { Unlink } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Url parser',
name: t('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: t('tools.url-parser.description'),
keywords: ['url', 'parser', 'protocol', 'origin', 'params', 'port', 'username', 'password', 'href'],
component: () => import('./url-parser.vue'),
icon: Unlink,

View file

@ -0,0 +1,17 @@
tools:
url-parser:
title: Url parser
description: Parse an url string to get all the different parts (protocol, origin, params, port, username-password, ...)
inputLabel: 'Your url to parse:'
inputPlaceholder: Your url to parse...
protocol: Protocol
username: Username
password: Password
hostname: Hostname
port: Port
path: Path
params: Params
inValidMessage: Invalid url

View file

@ -0,0 +1,17 @@
tools:
url-parser:
title: Url 解析器
description: '解析 URL 字符串以获取所有不同部分(协议,源,参数,端口,用户名-密码,...'
inputLabel: '要解析的 URL:'
inputPlaceholder: 要解析的 URL...
protocol: 协议
username: 用户名
password: 密码
hostname: 主机名
port: 端口
path: 路径
params: 参数
inValidMessage: 无效的 URL

View file

@ -3,24 +3,25 @@ import InputCopyable from '../../components/InputCopyable.vue';
import { isNotThrowing } from '@/utils/boolean';
import { withDefaultOnError } from '@/utils/defaults';
const { t } = useI18n();
const urlToParse = ref('https://me:pwd@it-tools.tech:3000/url-parser?key1=value&key2=value2#the-hash');
const urlParsed = computed(() => withDefaultOnError(() => new URL(urlToParse.value), undefined));
const urlValidationRules = [
{
validator: (value: string) => isNotThrowing(() => new URL(value)),
message: 'Invalid url',
message: t('tools.url-parser.inValidMessage'),
},
];
const properties: { title: string; key: keyof URL }[] = [
{ title: 'Protocol', key: 'protocol' },
{ title: 'Username', key: 'username' },
{ title: 'Password', key: 'password' },
{ title: 'Hostname', key: 'hostname' },
{ title: 'Port', key: 'port' },
{ title: 'Path', key: 'pathname' },
{ title: 'Params', key: 'search' },
{ title: t('tools.url-parser.protocol'), key: 'protocol' },
{ title: t('tools.url-parser.username'), key: 'username' },
{ title: t('tools.url-parser.password'), key: 'password' },
{ title: t('tools.url-parser.hostname'), key: 'hostname' },
{ title: t('tools.url-parser.port'), key: 'port' },
{ title: t('tools.url-parser.path'), key: 'pathname' },
{ title: t('tools.url-parser.params'), key: 'search' },
];
</script>
@ -28,8 +29,8 @@ const properties: { title: string; key: keyof URL }[] = [
<c-card>
<c-input-text
v-model:value="urlToParse"
label="Your url to parse:"
placeholder="Your url to parse..."
:label="t('tools.url-parser.inputLabel')"
:placeholder="t('tools.url-parser.inputPlaceholder')"
raw-text
:validation-rules="urlValidationRules"
/>

View file

@ -0,0 +1,4 @@
tools:
user-agent-parser:
title: User-agent parser
description: Detect and parse Browser, Engine, OS, CPU, and Device type/model from an user-agent string.

View file

@ -1,11 +1,11 @@
import { Fingerprint } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.uuid-generator.title'),
name: t('tools.uuid-generator.title'),
path: '/uuid-generator',
description: translate('tools.uuid-generator.description'),
description: t('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,

View file

@ -1,11 +1,11 @@
import { AlignJustified } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.yaml-to-json-converter.title'),
name: t('tools.yaml-to-json-converter.title'),
path: '/yaml-to-json-converter',
description: translate('tools.yaml-to-json-converter.description'),
description: t('tools.yaml-to-json-converter.description'),
keywords: ['yaml', 'to', 'json'],
component: () => import('./yaml-to-json.vue'),
icon: AlignJustified,

View file

@ -1,11 +1,11 @@
import { AlignJustified } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: translate('tools.yaml-to-toml.title'),
name: t('tools.yaml-to-toml.title'),
path: '/yaml-to-toml',
description: translate('tools.yaml-to-toml.title'),
description: t('tools.yaml-to-toml.title'),
keywords: ['yaml', 'to', 'toml', 'convert', 'transform'],
component: () => import('./yaml-to-toml.vue'),
icon: AlignJustified,