WIP(translate): translate web category all tools

This commit is contained in:
Amery2010 2024-02-18 18:16:53 +08:00
parent c6583ef013
commit 9db4b41daf
33 changed files with 1091 additions and 329 deletions

View file

@ -1,12 +1,12 @@
tools: tools:
hash-text: hash-text:
title: 文本转哈希 title: 文本转哈希
description: 使用所需的函数对文本字符串进行哈希计算MD5、SHA1、SHA256、SHA224、SHA512、SHA384、SHA3或RIPEMD160 description: '使用所需的函数对文本字符串进行哈希计算MD5、SHA1、SHA256、SHA224、SHA512、SHA384、SHA3或RIPEMD160'
textLabel: '要进行哈希的文本:' textLabel: '要进行哈希的文本:'
textPlaceholder: '要进行哈希的字符串...' textPlaceholder: '要进行哈希的字符串...'
hashLabel: 摘要编码 hashLabel: '摘要编码'
binary: 二进制基数2 binary: '二进制基数2'
hexadecimal: 十六进制基数16 hexadecimal: '十六进制基数16'
base64: Base64基数64 base64: 'Base64基数64'
base64url: Base64url带有URL安全字符的基数64 base64url: 'Base64url带有URL安全字符的基数64'

View file

@ -1,7 +1,7 @@
tools: tools:
html-entities: html-entities:
title: Escape html entities title: Escape html entities
description: Escape or unescape html entities (replace <,>, &, " and \' to their html version) description: Escape or unescape html entities (replace <,>, &, " and ' to their html version)
escape: escape:
title: Escape html entities title: Escape html entities

View file

@ -1,7 +1,7 @@
tools: tools:
html-entities: html-entities:
title: 转义 HTML 实体 title: 转义 HTML 实体
description: 转义或取消转义 HTML 实体(将 <,>, &, " 和 \' 替换为它们的 HTML 版本) description: 转义或取消转义 HTML 实体(将 <,>, &, " 和 ' 替换为它们的 HTML 版本)
escape: escape:
title: 转义 HTML 实体 title: 转义 HTML 实体

View file

@ -20,6 +20,7 @@ import {
} from '@vicons/tabler'; } from '@vicons/tabler';
import type { Component } from 'vue'; import type { Component } from 'vue';
import MenuBarItem from './menu-bar-item.vue'; import MenuBarItem from './menu-bar-item.vue';
import { translate as t } from '@/plugins/i18n.plugin';
const props = defineProps<{ editor: Editor }>(); const props = defineProps<{ editor: Editor }>();
const { editor } = toRefs(props); const { editor } = toRefs(props);
@ -38,28 +39,28 @@ const items: MenuItem[] = [
{ {
type: 'button', type: 'button',
icon: Bold, icon: Bold,
title: 'Bold', title: t('tools.html-wysiwyg-editor.bold'),
action: () => editor.value.chain().focus().toggleBold().run(), action: () => editor.value.chain().focus().toggleBold().run(),
isActive: () => editor.value.isActive('bold'), isActive: () => editor.value.isActive('bold'),
}, },
{ {
type: 'button', type: 'button',
icon: Italic, icon: Italic,
title: 'Italic', title: t('tools.html-wysiwyg-editor.italic'),
action: () => editor.value.chain().focus().toggleItalic().run(), action: () => editor.value.chain().focus().toggleItalic().run(),
isActive: () => editor.value.isActive('italic'), isActive: () => editor.value.isActive('italic'),
}, },
{ {
type: 'button', type: 'button',
icon: Strikethrough, icon: Strikethrough,
title: 'Strike', title: t('tools.html-wysiwyg-editor.strike'),
action: () => editor.value.chain().focus().toggleStrike().run(), action: () => editor.value.chain().focus().toggleStrike().run(),
isActive: () => editor.value.isActive('strike'), isActive: () => editor.value.isActive('strike'),
}, },
{ {
type: 'button', type: 'button',
icon: Code, icon: Code,
title: 'Inline code', title: t('tools.html-wysiwyg-editor.inlineCode'),
action: () => editor.value.chain().focus().toggleCode().run(), action: () => editor.value.chain().focus().toggleCode().run(),
isActive: () => editor.value.isActive('code'), isActive: () => editor.value.isActive('code'),
}, },
@ -69,28 +70,28 @@ const items: MenuItem[] = [
{ {
type: 'button', type: 'button',
icon: H1, icon: H1,
title: 'Heading 1', title: t('tools.html-wysiwyg-editor.heading1'),
action: () => editor.value.chain().focus().toggleHeading({ level: 1 }).run(), action: () => editor.value.chain().focus().toggleHeading({ level: 1 }).run(),
isActive: () => editor.value.isActive('heading', { level: 1 }), isActive: () => editor.value.isActive('heading', { level: 1 }),
}, },
{ {
type: 'button', type: 'button',
icon: H2, icon: H2,
title: 'Heading 2', title: t('tools.html-wysiwyg-editor.heading2'),
action: () => editor.value.chain().focus().toggleHeading({ level: 2 }).run(), action: () => editor.value.chain().focus().toggleHeading({ level: 2 }).run(),
isActive: () => editor.value.isActive('heading', { level: 2 }), isActive: () => editor.value.isActive('heading', { level: 2 }),
}, },
{ {
type: 'button', type: 'button',
icon: H3, icon: H3,
title: 'Heading 3', title: t('tools.html-wysiwyg-editor.heading3'),
action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(), action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(),
isActive: () => editor.value.isActive('heading', { level: 4 }), isActive: () => editor.value.isActive('heading', { level: 4 }),
}, },
{ {
type: 'button', type: 'button',
icon: H4, icon: H4,
title: 'Heading 4', title: t('tools.html-wysiwyg-editor.heading4'),
action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(), action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(),
isActive: () => editor.value.isActive('heading', { level: 4 }), isActive: () => editor.value.isActive('heading', { level: 4 }),
}, },
@ -100,21 +101,21 @@ const items: MenuItem[] = [
{ {
type: 'button', type: 'button',
icon: List, icon: List,
title: 'Bullet list', title: t('tools.html-wysiwyg-editor.bulletList'),
action: () => editor.value.chain().focus().toggleBulletList().run(), action: () => editor.value.chain().focus().toggleBulletList().run(),
isActive: () => editor.value.isActive('bulletList'), isActive: () => editor.value.isActive('bulletList'),
}, },
{ {
type: 'button', type: 'button',
icon: ListNumbers, icon: ListNumbers,
title: 'Ordered list', title: t('tools.html-wysiwyg-editor.orderedList'),
action: () => editor.value.chain().focus().toggleOrderedList().run(), action: () => editor.value.chain().focus().toggleOrderedList().run(),
isActive: () => editor.value.isActive('orderedList'), isActive: () => editor.value.isActive('orderedList'),
}, },
{ {
type: 'button', type: 'button',
icon: CodePlus, icon: CodePlus,
title: 'Code block', title: t('tools.html-wysiwyg-editor.codeBlock'),
action: () => editor.value.chain().focus().toggleCodeBlock().run(), action: () => editor.value.chain().focus().toggleCodeBlock().run(),
isActive: () => editor.value.isActive('codeBlock'), isActive: () => editor.value.isActive('codeBlock'),
}, },
@ -122,7 +123,7 @@ const items: MenuItem[] = [
{ {
type: 'button', type: 'button',
icon: Blockquote, icon: Blockquote,
title: 'Blockquote', title: t('tools.html-wysiwyg-editor.blockquote'),
action: () => editor.value.chain().focus().toggleBlockquote().run(), action: () => editor.value.chain().focus().toggleBlockquote().run(),
isActive: () => editor.value.isActive('blockquote'), isActive: () => editor.value.isActive('blockquote'),
}, },
@ -132,26 +133,26 @@ const items: MenuItem[] = [
{ {
type: 'button', type: 'button',
icon: TextWrap, icon: TextWrap,
title: 'Hard break', title: t('tools.html-wysiwyg-editor.hardBreak'),
action: () => editor.value.chain().focus().setHardBreak().run(), action: () => editor.value.chain().focus().setHardBreak().run(),
}, },
{ {
type: 'button', type: 'button',
icon: ClearFormatting, icon: ClearFormatting,
title: 'Clear format', title: t('tools.html-wysiwyg-editor.clearFormat'),
action: () => editor.value.chain().focus().clearNodes().unsetAllMarks().run(), action: () => editor.value.chain().focus().clearNodes().unsetAllMarks().run(),
}, },
{ {
type: 'button', type: 'button',
icon: ArrowBack, icon: ArrowBack,
title: 'Undo', title: t('tools.html-wysiwyg-editor.undo'),
action: () => editor.value.chain().focus().undo().run(), action: () => editor.value.chain().focus().undo().run(),
}, },
{ {
type: 'button', type: 'button',
icon: ArrowForwardUp, icon: ArrowForwardUp,
title: 'Redo', title: t('tools.html-wysiwyg-editor.redo'),
action: () => editor.value.chain().focus().redo().run(), action: () => editor.value.chain().focus().redo().run(),
}, },
]; ];

View file

@ -1,10 +1,11 @@
import { Edit } from '@vicons/tabler'; import { Edit } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'HTML WYSIWYG editor', name: t('tools.html-wysiwyg-editor.title'),
path: '/html-wysiwyg-editor', path: '/html-wysiwyg-editor',
description: 'Online HTML editor with feature-rich WYSIWYG editor, get the source code of the content immediately.', description: t('tools.html-wysiwyg-editor.description'),
keywords: ['html', 'wysiwyg', 'editor', 'p', 'ul', 'ol', 'converter', 'live'], keywords: ['html', 'wysiwyg', 'editor', 'p', 'ul', 'ol', 'converter', 'live'],
component: () => import('./html-wysiwyg-editor.vue'), component: () => import('./html-wysiwyg-editor.vue'),
icon: Edit, icon: Edit,

View file

@ -2,3 +2,20 @@ tools:
html-wysiwyg-editor: html-wysiwyg-editor:
title: HTML WYSIWYG editor title: HTML WYSIWYG editor
description: Online HTML editor with feature-rich WYSIWYG editor, get the source code of the content immediately. description: Online HTML editor with feature-rich WYSIWYG editor, get the source code of the content immediately.
bold: Bold
italic: Italic
strike: Strike
inlineCode: Inline code
heading1: Heading 1
heading2: Heading 2
heading3: Heading 3
heading4: Heading 4
bulletList: Bullet list
orderedList: Ordered list
codeBlock: Code block
blockquote: Blockquote
hardBreak: Hard break
clearFormat: Clear format
undo: Undo
redo: Redo

View file

@ -0,0 +1,21 @@
tools:
html-wysiwyg-editor:
title: HTML所见即所得编辑器
description: 在线 HTML 编辑器,具有功能丰富的所见即所得编辑器,可立即获取内容的源代码。
bold: 粗体
italic: 斜体
strike: 删除线
inlineCode: 行内代码
heading1: 标题 1
heading2: 标题 2
heading3: 标题 3
heading4: 标题 4
bulletList: 无序列表
orderedList: 有序列表
codeBlock: 代码块
blockquote: 引用块
hardBreak: 强制换行
clearFormat: 清除格式
undo: 撤销
redo: 重做

View file

@ -1,3 +1,5 @@
import { translate as t } from '@/plugins/i18n.plugin';
export const codesByCategories: { export const codesByCategories: {
category: string category: string
codes: { codes: {
@ -8,423 +10,404 @@ export const codesByCategories: {
}[] }[]
}[] = [ }[] = [
{ {
category: '1xx informational response', category: t('tools.http-status-codes.1xx'),
codes: [ codes: [
{ {
code: 100, code: 100,
name: 'Continue', name: t('tools.http-status-codes.100.name'),
description: 'Waiting for the client to emit the body of the request.', description: t('tools.http-status-codes.100.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 101, code: 101,
name: 'Switching Protocols', name: t('tools.http-status-codes.101.name'),
description: 'The server has agreed to change protocol.', description: t('tools.http-status-codes.101.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 102, code: 102,
name: 'Processing', name: t('tools.http-status-codes.102.name'),
description: 'The server is processing the request, but no response is available yet.', description: t('tools.http-status-codes.102.description'),
type: 'WebDav', type: 'WebDav',
}, },
{ {
code: 103, code: 103,
name: 'Early Hints', name: t('tools.http-status-codes.103.name'),
description: 'The server returns some response headers before final HTTP message.', description: t('tools.http-status-codes.103.description'),
type: 'HTTP', type: 'HTTP',
}, },
], ],
}, },
{ {
category: '2xx success', category: t('tools.http-status-codes.2xx'),
codes: [ codes: [
{ {
code: 200, code: 200,
name: 'OK', name: t('tools.http-status-codes.200.name'),
description: 'Standard response for successful HTTP requests.', description: t('tools.http-status-codes.200.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 201, code: 201,
name: 'Created', name: t('tools.http-status-codes.201.name'),
description: 'The request has been fulfilled, resulting in the creation of a new resource.', description: t('tools.http-status-codes.201.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 202, code: 202,
name: 'Accepted', name: t('tools.http-status-codes.202.name'),
description: 'The request has been accepted for processing, but the processing has not been completed.', description: t('tools.http-status-codes.202.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 203, code: 203,
name: 'Non-Authoritative Information', name: t('tools.http-status-codes.203.name'),
description: description: t('tools.http-status-codes.203.description'),
'The request is successful but the content of the original request has been modified by a transforming proxy.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 204, code: 204,
name: 'No Content', name: t('tools.http-status-codes.204.name'),
description: 'The server successfully processed the request and is not returning any content.', description: t('tools.http-status-codes.204.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 205, code: 205,
name: 'Reset Content', name: t('tools.http-status-codes.205.name'),
description: 'The server indicates to reinitialize the document view which sent this request.', description: t('tools.http-status-codes.205.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 206, code: 206,
name: 'Partial Content', name: t('tools.http-status-codes.206.name'),
description: 'The server is delivering only part of the resource due to a range header sent by the client.', description: t('tools.http-status-codes.206.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 207, code: 207,
name: 'Multi-Status', name: t('tools.http-status-codes.207.name'),
description: description: t('tools.http-status-codes.207.description'),
'The message body that follows is an XML message and can contain a number of separate response codes.',
type: 'WebDav', type: 'WebDav',
}, },
{ {
code: 208, code: 208,
name: 'Already Reported', name: t('tools.http-status-codes.208.name'),
description: description: t('tools.http-status-codes.208.description'),
'The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response.',
type: 'WebDav', type: 'WebDav',
}, },
{ {
code: 226, code: 226,
name: 'IM Used', name: t('tools.http-status-codes.226.name'),
description: description: t('tools.http-status-codes.226.description'),
'The server has fulfilled a request for the resource, and the response is a representation of the result.',
type: 'HTTP', type: 'HTTP',
}, },
], ],
}, },
{ {
category: '3xx redirection', category: t('tools.http-status-codes.3xx'),
codes: [ codes: [
{ {
code: 300, code: 300,
name: 'Multiple Choices', name: t('tools.http-status-codes.300.name'),
description: 'Indicates multiple options for the resource that the client may follow.', description: t('tools.http-status-codes.300.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 301, code: 301,
name: 'Moved Permanently', name: t('tools.http-status-codes.301.name'),
description: 'This and all future requests should be directed to the given URI.', description: t('tools.http-status-codes.301.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 302, code: 302,
name: 'Found', name: t('tools.http-status-codes.302.name'),
description: 'Redirect to another URL. This is an example of industry practice contradicting the standard.', description: t('tools.http-status-codes.302.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 303, code: 303,
name: 'See Other', name: t('tools.http-status-codes.303.name'),
description: 'The response to the request can be found under another URI using a GET method.', description: t('tools.http-status-codes.303.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 304, code: 304,
name: 'Not Modified', name: t('tools.http-status-codes.304.name'),
description: description: t('tools.http-status-codes.304.description'),
'Indicates that the resource has not been modified since the version specified by the request headers.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 305, code: 305,
name: 'Use Proxy', name: t('tools.http-status-codes.305.name'),
description: description: t('tools.http-status-codes.305.description'),
'The requested resource is available only through a proxy, the address for which is provided in the response.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 306, code: 306,
name: 'Switch Proxy', name: t('tools.http-status-codes.306.name'),
description: 'No longer used. Originally meant "Subsequent requests should use the specified proxy."', description: t('tools.http-status-codes.306.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 307, code: 307,
name: 'Temporary Redirect', name: t('tools.http-status-codes.307.name'),
description: description: t('tools.http-status-codes.307.description'),
'In this case, the request should be repeated with another URI; however, future requests should still use the original URI.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 308, code: 308,
name: 'Permanent Redirect', name: t('tools.http-status-codes.308.name'),
description: 'The request and all future requests should be repeated using another URI.', description: t('tools.http-status-codes.308.description'),
type: 'HTTP', type: 'HTTP',
}, },
], ],
}, },
{ {
category: '4xx client error', category: t('tools.http-status-codes.4xx'),
codes: [ codes: [
{ {
code: 400, code: 400,
name: 'Bad Request', name: t('tools.http-status-codes.400.name'),
description: 'The server cannot or will not process the request due to an apparent client error.', description: t('tools.http-status-codes.400.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 401, code: 401,
name: 'Unauthorized', name: t('tools.http-status-codes.401.name'),
description: description: t('tools.http-status-codes.401.description'),
'Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 402, code: 402,
name: 'Payment Required', name: t('tools.http-status-codes.402.name'),
description: description: t('tools.http-status-codes.402.description'),
'Reserved for future use. The original intention was that this code might be used as part of some form of digital cash or micropayment scheme.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 403, code: 403,
name: 'Forbidden', name: t('tools.http-status-codes.403.name'),
description: description: t('tools.http-status-codes.403.description'),
'The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 404, code: 404,
name: 'Not Found', name: t('tools.http-status-codes.404.name'),
description: 'The requested resource could not be found but may be available in the future.', description: t('tools.http-status-codes.404.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 405, code: 405,
name: 'Method Not Allowed', name: t('tools.http-status-codes.405.name'),
description: 'A request method is not supported for the requested resource.', description: t('tools.http-status-codes.405.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 406, code: 406,
name: 'Not Acceptable', name: t('tools.http-status-codes.406.name'),
description: description: t('tools.http-status-codes.406.description'),
'The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 407, code: 407,
name: 'Proxy Authentication Required', name: t('tools.http-status-codes.407.name'),
description: 'The client must first authenticate itself with the proxy.', description: t('tools.http-status-codes.407.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 408, code: 408,
name: 'Request Timeout', name: t('tools.http-status-codes.408.name'),
description: 'The server timed out waiting for the request.', description: t('tools.http-status-codes.408.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 409, code: 409,
name: 'Conflict', name: t('tools.http-status-codes.409.name'),
description: description: t('tools.http-status-codes.409.description'),
'Indicates that the request could not be processed because of conflict in the request, such as an edit conflict.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 410, code: 410,
name: 'Gone', name: t('tools.http-status-codes.410.name'),
description: 'Indicates that the resource requested is no longer available and will not be available again.', description: t('tools.http-status-codes.410.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 411, code: 411,
name: 'Length Required', name: t('tools.http-status-codes.411.name'),
description: description: t('tools.http-status-codes.411.description'),
'The request did not specify the length of its content, which is required by the requested resource.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 412, code: 412,
name: 'Precondition Failed', name: t('tools.http-status-codes.412.name'),
description: 'The server does not meet one of the preconditions that the requester put on the request.', description: t('tools.http-status-codes.412.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 413, code: 413,
name: 'Payload Too Large', name: t('tools.http-status-codes.413.name'),
description: 'The request is larger than the server is willing or able to process.', description: t('tools.http-status-codes.413.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 414, code: 414,
name: 'URI Too Long', name: t('tools.http-status-codes.414.name'),
description: 'The URI provided was too long for the server to process.', description: t('tools.http-status-codes.414.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 415, code: 415,
name: 'Unsupported Media Type', name: t('tools.http-status-codes.415.name'),
description: 'The request entity has a media type which the server or resource does not support.', description: t('tools.http-status-codes.415.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 416, code: 416,
name: 'Range Not Satisfiable', name: t('tools.http-status-codes.416.name'),
description: 'The client has asked for a portion of the file, but the server cannot supply that portion.', description: t('tools.http-status-codes.416.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 417, code: 417,
name: 'Expectation Failed', name: t('tools.http-status-codes.417.name'),
description: 'The server cannot meet the requirements of the Expect request-header field.', description: t('tools.http-status-codes.417.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 418, code: 418,
name: 'I\'m a teapot', name: t('tools.http-status-codes.418.name'),
description: 'The server refuses the attempt to brew coffee with a teapot.', description: t('tools.http-status-codes.418.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 421, code: 421,
name: 'Misdirected Request', name: t('tools.http-status-codes.421.name'),
description: 'The request was directed at a server that is not able to produce a response.', description: t('tools.http-status-codes.421.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 422, code: 422,
name: 'Unprocessable Entity', name: t('tools.http-status-codes.422.name'),
description: 'The request was well-formed but was unable to be followed due to semantic errors.', description: t('tools.http-status-codes.422.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 423, code: 423,
name: 'Locked', name: t('tools.http-status-codes.423.name'),
description: 'The resource that is being accessed is locked.', description: t('tools.http-status-codes.423.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 424, code: 424,
name: 'Failed Dependency', name: t('tools.http-status-codes.424.name'),
description: 'The request failed due to failure of a previous request.', description: t('tools.http-status-codes.424.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 425, code: 425,
name: 'Too Early', name: t('tools.http-status-codes.425.name'),
description: 'Indicates that the server is unwilling to risk processing a request that might be replayed.', description: t('tools.http-status-codes.425.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 426, code: 426,
name: 'Upgrade Required', name: t('tools.http-status-codes.426.name'),
description: 'The client should switch to a different protocol such as TLS/1.0.', description: t('tools.http-status-codes.426.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 428, code: 428,
name: 'Precondition Required', name: t('tools.http-status-codes.428.name'),
description: 'The origin server requires the request to be conditional.', description: t('tools.http-status-codes.428.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 429, code: 429,
name: 'Too Many Requests', name: t('tools.http-status-codes.429.name'),
description: 'The user has sent too many requests in a given amount of time.', description: t('tools.http-status-codes.429.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 431, code: 431,
name: 'Request Header Fields Too Large', name: t('tools.http-status-codes.431.name'),
description: description: t('tools.http-status-codes.431.description'),
'The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 451, code: 451,
name: 'Unavailable For Legal Reasons', name: t('tools.http-status-codes.451.name'),
description: description: t('tools.http-status-codes.451.description'),
'A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.',
type: 'HTTP', type: 'HTTP',
}, },
], ],
}, },
{ {
category: '5xx server error', category: t('tools.http-status-codes.5xx'),
codes: [ codes: [
{ {
code: 500, code: 500,
name: 'Internal Server Error', name: t('tools.http-status-codes.500.name'),
description: description: t('tools.http-status-codes.500.description'),
'A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 501, code: 501,
name: 'Not Implemented', name: t('tools.http-status-codes.501.name'),
description: description: t('tools.http-status-codes.501.description'),
'The server either does not recognize the request method, or it lacks the ability to fulfill the request.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 502, code: 502,
name: 'Bad Gateway', name: t('tools.http-status-codes.502.name'),
description: description: t('tools.http-status-codes.502.description'),
'The server was acting as a gateway or proxy and received an invalid response from the upstream server.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 503, code: 503,
name: 'Service Unavailable', name: t('tools.http-status-codes.503.name'),
description: 'The server is currently unavailable (because it is overloaded or down for maintenance).', description: t('tools.http-status-codes.503.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 504, code: 504,
name: 'Gateway Timeout', name: t('tools.http-status-codes.504.name'),
description: description: t('tools.http-status-codes.504.description'),
'The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.',
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 505, code: 505,
name: 'HTTP Version Not Supported', name: t('tools.http-status-codes.505.name'),
description: 'The server does not support the HTTP protocol version used in the request.', description: t('tools.http-status-codes.505.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 506, code: 506,
name: 'Variant Also Negotiates', name: t('tools.http-status-codes.506.name'),
description: 'Transparent content negotiation for the request results in a circular reference.', description: t('tools.http-status-codes.506.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 507, code: 507,
name: 'Insufficient Storage', name: t('tools.http-status-codes.507.name'),
description: 'The server is unable to store the representation needed to complete the request.', description: t('tools.http-status-codes.507.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 508, code: 508,
name: 'Loop Detected', name: t('tools.http-status-codes.508.name'),
description: 'The server detected an infinite loop while processing the request.', description: t('tools.http-status-codes.508.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 510, code: 510,
name: 'Not Extended', name: t('tools.http-status-codes.510.name'),
description: 'Further extensions to the request are required for the server to fulfill it.', description: t('tools.http-status-codes.510.description'),
type: 'HTTP', type: 'HTTP',
}, },
{ {
code: 511, code: 511,
name: 'Network Authentication Required', name: t('tools.http-status-codes.511.name'),
description: 'The client needs to authenticate to gain network access.', description: t('tools.http-status-codes.511.description'),
type: 'HTTP', type: 'HTTP',
}, },
], ],

View file

@ -1,8 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { codesByCategories } from './http-status-codes.constants'; import * as status from './http-status-codes.constants';
import { useFuzzySearch } from '@/composable/fuzzySearch'; import { useFuzzySearch } from '@/composable/fuzzySearch';
const search = ref(''); const search = ref('');
const { t } = useI18n();
const { codesByCategories } = status;
const { searchResult } = useFuzzySearch({ const { searchResult } = useFuzzySearch({
search, search,
@ -17,7 +19,7 @@ const codesByCategoryFiltered = computed(() => {
return codesByCategories; return codesByCategories;
} }
return [{ category: 'Search results', codes: searchResult.value }]; return [{ category: t('tools.http-status-codes.searchResults'), codes: searchResult.value }];
}); });
</script> </script>
@ -25,7 +27,7 @@ const codesByCategoryFiltered = computed(() => {
<div> <div>
<c-input-text <c-input-text
v-model:value="search" v-model:value="search"
placeholder="Search http status..." :placeholder="t('tools.http-status-codes.searchPlaceholder')"
autofocus raw-text mb-10 autofocus raw-text mb-10
/> />

View file

@ -1,17 +1,18 @@
import { HttpRound } from '@vicons/material'; import { HttpRound } from '@vicons/material';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { codesByCategories } from './http-status-codes.constants'; // import { codesByCategories } from './http-status-codes.constants';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'HTTP status codes', name: t('tools.http-status-codes.title'),
path: '/http-status-codes', path: '/http-status-codes',
description: 'The list of all HTTP status codes their name and their meaning.', description: t('tools.http-status-codes.description'),
keywords: [ keywords: [
'http', 'http',
'status', 'status',
'codes', 'codes',
...codesByCategories.flatMap(({ codes }) => codes.flatMap(({ code, name }) => [String(code), name])), // ...codesByCategories.flatMap(({ codes }) => codes.flatMap(({ code, name }) => [String(code), name])),
], ],
component: () => import('./http-status-codes.vue'), component: () => import('./http-status-codes.vue'),
icon: HttpRound, icon: HttpRound,

View file

@ -2,3 +2,201 @@ tools:
http-status-codes: http-status-codes:
title: HTTP status codes title: HTTP status codes
description: The list of all HTTP status codes their name and their meaning. description: The list of all HTTP status codes their name and their meaning.
searchPlaceholder: Search http status...
searchResults: Search results
1xx: 1xx informational response
100:
name: Continue
description: Waiting for the client to emit the body of the request.
101:
name: Switching Protocols
description: The server has agreed to change protocol.
102:
name: Processing
description: The server is processing the request, but no response is available yet.
103:
name: Early Hints
description: The server returns some response headers before final HTTP message.
2xx: 2xx success
200:
name: OK
description: Standard response for successful HTTP requests.
201:
name: Created
description: The request has been fulfilled, resulting in the creation of a new resource.
202:
name: Accepted
description: The request has been accepted for processing, but the processing has not been completed.
203:
name: Non-Authoritative Information
description: The request is successful but the content of the original request has been modified by a transforming proxy.
204:
name: No Content
description: The server successfully processed the request and is not returning any content.
205:
name: Reset Content
description: The server indicates to reinitialize the document view which sent this request.
206:
name: Partial Content
description: The server is delivering only part of the resource due to a range header sent by the client.
207:
name: Multi-Status
description: The message body that follows is an XML message and can contain a number of separate response codes.
208:
name: Already Reported
description: The members of a DAV binding have already been enumerated in a preceding part of the (multistatus) response.
226:
name: IM Used
description: The server has fulfilled a request for the resource, and the response is a representation of the result.
3xx: 3xx redirection
300:
name: Multiple Choices
description: Indicates multiple options for the resource that the client may follow.
301:
name: Moved Permanently
description: This and all future requests should be directed to the given URI.
302:
name: Found
description: Redirect to another URL. This is an example of industry practice contradicting the standard.
303:
name: See Other
description: The response to the request can be found under another URI using a GET method.
304:
name: Not Modified
description: Indicates that the resource has not been modified since the version specified by the request headers.
305:
name: Use Proxy
description: The requested resource is available only through a proxy, the address for which is provided in the response.
306:
name: Switch Proxy
description: No longer used. Originally meant "Subsequent requests should use the specified proxy."
307:
name: Temporary Redirect
description: In this case, the request should be repeated with another URI; however, future requests should still use the original URI.
308:
name: Permanent Redirect
description: The request and all future requests should be repeated using another URI.
4xx: 4xx client error
400:
name: Bad Request
description: The server cannot or will not process the request due to an apparent client error.
401:
name: Unauthorized
description: Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided.
402:
name: Payment Required
description: Reserved for future use. The original intention was that this code might be used as part of some form of digital cash or micropayment scheme.
403:
name: Forbidden
description: The request was valid, but the server is refusing action. The user might not have the necessary permissions for a resource.
404:
name: Not Found
description: The requested resource could not be found but may be available in the future.
405:
name: Method Not Allowed
description: A request method is not supported for the requested resource.
406:
name: Not Acceptable
description: The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.
407:
name: Proxy Authentication Required
description: The client must first authenticate itself with the proxy.
408:
name: Request Timeout
description: The server timed out waiting for the request.
409:
name: Conflict
description: Indicates that the request could not be processed because of conflict in the request, such as an edit conflict.
410:
name: Gone
description: Indicates that the resource requested is no longer available and will not be available again.
411:
name: Length Required
description: The request did not specify the length of its content, which is required by the requested resource.
412:
name: Precondition Failed
description: The server does not meet one of the preconditions that the requester put on the request.
413:
name: Payload Too Large
description: The request is larger than the server is willing or able to process.
414:
name: URI Too Long
description: The URI provided was too long for the server to process.
415:
name: Unsupported Media Type
description: The request entity has a media type which the server or resource does not support.
416:
name: Range Not Satisfiable
description: The client has asked for a portion of the file, but the server cannot supply that portion.
417:
name: Expectation Failed
description: The server cannot meet the requirements of the Expect request-header field.
418:
name: I'm a teapot
description: The server refuses the attempt to brew coffee with a teapot.
421:
name: Misdirected Request
description: The request was directed at a server that is not able to produce a response.
422:
name: Unprocessable Entity
description: The request was well-formed but was unable to be followed due to semantic errors.
423:
name: Locked
description: The resource that is being accessed is locked.
424:
name: Failed Dependency
description: The request failed due to failure of a previous request.
425:
name: Too Early
description: Indicates that the server is unwilling to risk processing a request that might be replayed.
426:
name: Upgrade Required
description: The client should switch to a different protocol such as TLS/1.0.
428:
name: Precondition Required
description: The origin server requires the request to be conditional.
429:
name: Too Many Requests
description: The user has sent too many requests in a given amount of time.
431:
name: Request Header Fields Too Large
description: The server is unwilling to process the request because either an individual header field, or all the header fields collectively, are too large.
451:
name: Unavailable For Legal Reasons
description: A server operator has received a legal demand to deny access to a resource or to a set of resources that includes the requested resource.
5xx: 5xx server error
500:
name: Internal Server Error
description: A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
501:
name: Not Implemented
description: The server either does not recognize the request method, or it lacks the ability to fulfill the request.
502:
name: Bad Gateway
description: The server was acting as a gateway or proxy and received an invalid response from the upstream server.
503:
name: Service Unavailable
description: The server is currently unavailable (because it is overloaded or down for maintenance).
504:
name: Gateway Timeout
description: The server was acting as a gateway or proxy and did not receive a timely response from the upstream server.
505:
name: HTTP Version Not Supported
description: The server does not support the HTTP protocol version used in the request.
506:
name: Variant Also Negotiates
description: Transparent content negotiation for the request results in a circular reference.
507:
name: Insufficient Storage
description: The server is unable to store the representation needed to complete the request.
508:
name: Loop Detected
description: The server detected an infinite loop while processing the request.
510:
name: Not Extended
description: Further extensions to the request are required for the server to fulfill it.
511:
name: Network Authentication Required
description: The client needs to authenticate to gain network access.

View file

@ -0,0 +1,202 @@
tools:
http-status-codes:
title: HTTP 状态码
description: 所有 HTTP 状态码及其名称和含义的列表。
searchPlaceholder: 搜索 HTTP 状态码...
searchResults: 搜索结果
1xx: 1xx 信息性响应
100:
name: 继续
description: 等待客户端发送请求主体。
101:
name: 切换协议
description: 服务器已同意切换协议。
102:
name: 处理中
description: 服务器正在处理请求,但尚未有响应可用。
103:
name: 早期提示
description: 服务器在最终 HTTP 消息之前返回一些响应头。
2xx: 2xx 成功
200:
name: OK
description: 成功的标准 HTTP 请求响应。
201:
name: 已创建
description: 请求已成功,导致创建新资源。
202:
name: 已接受
description: 请求已被接受处理,但处理尚未完成。
203:
name: 非权威信息
description: 请求成功,但原始请求的内容已被转换代理修改。
204:
name: 无内容
description: 服务器成功处理请求,但未返回任何内容。
205:
name: 重置内容
description: 服务器指示重新初始化发送此请求的文档视图。
206:
name: 部分内容
description: 服务器仅因客户端发送的范围头而传送资源的部分。
207:
name: 多状态
description: 接下来的消息主体是 XML 消息,可以包含许多独立的响应代码。
208:
name: 已报告
description: DAV 绑定的成员已在响应的前一部分中枚举。
226:
name: 使用 IM
description: 服务器已满足对资源的请求,响应是结果的表示。
3xx: 3xx 重定向
300:
name: 多种选择
description: 表示客户端可以跟随的资源的多个选项。
301:
name: 永久移动
description: 此次及所有后续请求应重定向到给定的 URI。
302:
name: 找到
description: 重定向到另一个 URL。这是一个违反标准的行业实践示例。
303:
name: 查看其他
description: 请求的响应可以在另一个 URI 下使用 GET 方法找到。
304:
name: 未修改
description: 表示资源自请求标头指定的版本以来未被修改。
305:
name: 使用代理
description: 请求的资源仅通过代理提供,响应中提供了代理的地址。
306:
name: 切换代理
description: 不再使用。最初的含义是“后续请求应使用指定的代理”。
307:
name: 临时重定向
description: 在这种情况下,应重复使用另一个 URI 进行请求;但是,将来的请求仍应使用原始 URI。
308:
name: 永久重定向
description: 请求和所有后续请求应重复使用另一个 URI。
4xx: 4xx 客户端错误
400:
name: 错误请求
description: 由于明显的客户端错误,服务器无法处理请求或不会处理请求。
401:
name: 未经授权
description: 类似于 403 Forbidden但专门用于在需要验证但验证失败或尚未提供的情况下使用。
402:
name: 需要付款
description: 保留以备将来使用。最初的意图是,该代码可能作为某种数字货币或微支付方案的一部分使用。
403:
name: 禁止
description: 请求有效,但服务器拒绝执行操作。用户可能没有资源所需的必要权限。
404:
name: 未找到
description: 请求的资源未找到,但将来可能会可用。
405:
name: 方法不允许
description: 请求方法不受请求资源支持。
406:
name: 不可接受
description: 请求的资源能够生成的内容不符合请求中发送的 Accept 标头。
407:
name: 需要代理身份验证
description: 客户端必须首先通过代理进行身份验证。
408:
name: 请求超时
description: 服务器在等待请求时超时。
409:
name: 冲突
description: 表示由于请求中的冲突(如编辑冲突)无法处理请求。
410:
name: 已消失
description: 表示请求的资源不再可用,也不会再次可用。
411:
name: 需要长度
description: 请求未指定所需资源的长度。
412:
name: 前提条件失败
description: 服务器未满足请求者对请求的其中一个前提条件。
413:
name: 负载过大
description: 请求的大小超过服务器愿意或能够处理的范围。
414:
name: URI 过长
description: 提供的 URI 对服务器来说太长,无法处理。
415:
name: 不支持的媒体类型
description: 请求实体具有服务器或资源不支持的媒体类型。
416:
name: 范围不可满足
description: 客户端请求文件的一部分,但服务器无法提供该部分。
417:
name: 预期失败
description: 服务器无法满足 Expect 请求标头字段的要求。
418:
name: 我是茶壶
description: 服务器拒绝使用茶壶煮咖啡。
421:
name: 请求错误
description: 请求指向无法生成响应的服务器。
422:
name: 无法处理的实体
description: 请求格式正确,但由于语义错误无法进行后续处理。
423:
name: 已锁定
description: 正在访问的资源已被锁定。
424:
name: 失败的依赖
description: 由于先前请求的失败,请求失败。
425:
name: 太早
description: 表示服务器不愿冒险处理可能会被重播的请求。
426:
name: 需要升级
description: 客户端应切换到其他协议,如 TLS/1.0。
428:
name: 需要前提条件
description: 源服务器要求请求是有条件的。
429:
name: 请求过多
description: 用户在一定时间内发送了太多请求。
431:
name: 请求头字段太大
description: 服务器不愿处理请求,因为单个标头字段或所有标头字段的总和太大。
451:
name: 因法律原因不可用
description: 服务器操作员收到法律要求拒绝访问资源或包含请求的资源集。
5xx: 5xx 服务器错误
500:
name: 内部服务器错误
description: 遇到意外情况并且没有更具体消息适用时给出的通用错误消息。
501:
name: 未实现
description: 服务器不识别请求方法,或缺乏实现请求的能力。
502:
name: 错误网关
description: 服务器作为网关或代理行事,并从上游服务器接收到无效响应。
503:
name: 服务不可用
description: 服务器当前不可用(因为过载或正在维护)。
504:
name: 网关超时
description: 服务器作为网关或代理行事,未从上游服务器收到及时响应。
505:
name: 不支持的 HTTP 版本
description: 服务器不支持请求中使用的 HTTP 协议版本。
506:
name: 变体也进行协商
description: 用于请求结果的透明内容协商导致循环引用。
507:
name: 存储空间不足
description: 服务器无法存储完成请求所需的表示。
508:
name: 检测到循环
description: 服务器在处理请求时检测到无限循环。
510:
name: 未扩展
description: 服务器需要进一步扩展请求才能满足它。
511:
name: 需要网络认证
description: 客户端需要进行身份验证以获得网络访问权限。

View file

@ -5,6 +5,8 @@ import { DiffRootViewer } from './diff-viewer.models';
import { useAppTheme } from '@/ui/theme/themes'; import { useAppTheme } from '@/ui/theme/themes';
const props = defineProps<{ leftJson: unknown; rightJson: unknown }>(); const props = defineProps<{ leftJson: unknown; rightJson: unknown }>();
const { t } = useI18n();
const onlyShowDifferences = ref(false); const onlyShowDifferences = ref(false);
const { leftJson, rightJson } = toRefs(props); const { leftJson, rightJson } = toRefs(props);
const appTheme = useAppTheme(); const appTheme = useAppTheme();
@ -20,14 +22,14 @@ const showResults = computed(() => !_.isUndefined(leftJson.value) && !_.isUndefi
<template> <template>
<div v-if="showResults"> <div v-if="showResults">
<div flex justify-center> <div flex justify-center>
<n-form-item label="Only show differences" label-placement="left"> <n-form-item :label="t('tools.json-diff.onlyShowDifferences')" label-placement="left">
<n-switch v-model:value="onlyShowDifferences" /> <n-switch v-model:value="onlyShowDifferences" />
</n-form-item> </n-form-item>
</div> </div>
<c-card data-test-id="diff-result"> <c-card data-test-id="diff-result">
<div v-if="jsonAreTheSame" text-center op-70> <div v-if="jsonAreTheSame" text-center op-70>
The provided JSONs are the same {{ t('tools.json-diff.jsonAreTheSame') }}
</div> </div>
<DiffRootViewer v-else :diff="result" /> <DiffRootViewer v-else :diff="result" />
</c-card> </c-card>

View file

@ -1,10 +1,11 @@
import { CompareArrowsRound } from '@vicons/material'; import { CompareArrowsRound } from '@vicons/material';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'JSON diff', name: t('tools.json-diff.title'),
path: '/json-diff', path: '/json-diff',
description: 'Compare two JSON objects and get the differences between them.', description: t('tools.json-diff.description'),
keywords: ['json', 'diff', 'compare', 'difference', 'object', 'data'], keywords: ['json', 'diff', 'compare', 'difference', 'object', 'data'],
component: () => import('./json-diff.vue'), component: () => import('./json-diff.vue'),
icon: CompareArrowsRound, icon: CompareArrowsRound,

View file

@ -5,6 +5,8 @@ import DiffsViewer from './diff-viewer/diff-viewer.vue';
import { withDefaultOnError } from '@/utils/defaults'; import { withDefaultOnError } from '@/utils/defaults';
import { isNotThrowing } from '@/utils/boolean'; import { isNotThrowing } from '@/utils/boolean';
const { t } = useI18n();
const rawLeftJson = ref(''); const rawLeftJson = ref('');
const rawRightJson = ref(''); const rawRightJson = ref('');
@ -14,7 +16,7 @@ const rightJson = computed(() => withDefaultOnError(() => JSON5.parse(rawRightJs
const jsonValidationRules = [ const jsonValidationRules = [
{ {
validator: (value: string) => value === '' || isNotThrowing(() => JSON5.parse(value)), validator: (value: string) => value === '' || isNotThrowing(() => JSON5.parse(value)),
message: 'Invalid JSON format', message: t('tools.json-diff.invalidJSONFormat'),
}, },
]; ];
</script> </script>
@ -23,8 +25,8 @@ const jsonValidationRules = [
<c-input-text <c-input-text
v-model:value="rawLeftJson" v-model:value="rawLeftJson"
:validation-rules="jsonValidationRules" :validation-rules="jsonValidationRules"
label="Your first JSON" :label="t('tools.json-diff.inputLable')"
placeholder="Paste your first JSON here..." :placeholder="t('tools.json-diff.inputPlaceholder')"
rows="20" rows="20"
multiline multiline
test-id="leftJson" test-id="leftJson"
@ -35,8 +37,8 @@ const jsonValidationRules = [
<c-input-text <c-input-text
v-model:value="rawRightJson" v-model:value="rawRightJson"
:validation-rules="jsonValidationRules" :validation-rules="jsonValidationRules"
label="Your JSON to compare" :label="t('tools.json-diff.outputLable')"
placeholder="Paste your JSON to compare here..." :placeholder="t('tools.json-diff.outputPlaceholder')"
rows="20" rows="20"
multiline multiline
test-id="rightJson" test-id="rightJson"

View file

@ -2,3 +2,12 @@ tools:
json-diff: json-diff:
title: JSON diff title: JSON diff
description: Compare two JSON objects and get the differences between them. description: Compare two JSON objects and get the differences between them.
inputLable: Your first JSON
inputPlaceholder: Paste your first JSON here...
outputLable: Your JSON to compare
outputPlaceholder: Paste your JSON to compare here...
onlyShowDifferences: Only show differences
jsonAreTheSame: The provided JSONs are the same
invalidJSONFormat: Invalid JSON format

View file

@ -0,0 +1,13 @@
tools:
json-diff:
title: JSON 对比
description: 比较两个 JSON 对象并获取它们之间的差异。
inputLable: 您的第一个 JSON
inputPlaceholder: 在此粘贴您的第一个 JSON...
outputLable: 您要比较的 JSON
outputPlaceholder: 在此粘贴要比较的 JSON...
onlyShowDifferences: 仅显示差异
jsonAreTheSame: 提供的 JSON 相同
invalidJSONFormat: 无效的 JSON 格式

View file

@ -1,92 +1,94 @@
import { translate as t } from '@/plugins/i18n.plugin';
// From https://datatracker.ietf.org/doc/html/rfc7518#section-3.1 // From https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
export const ALGORITHM_DESCRIPTIONS: { [k: string]: string } = { export const ALGORITHM_DESCRIPTIONS: { [k: string]: string } = {
HS256: 'HMAC using SHA-256', HS256: t('tools.jwt-parser.HS256'),
HS384: 'HMAC using SHA-384', HS384: t('tools.jwt-parser.HS384'),
HS512: 'HMAC using SHA-512', HS512: t('tools.jwt-parser.HS512'),
RS256: 'RSASSA-PKCS1-v1_5 using SHA-256', RS256: t('tools.jwt-parser.RS256'),
RS384: 'RSASSA-PKCS1-v1_5 using SHA-384', RS384: t('tools.jwt-parser.RS384'),
RS512: 'RSASSA-PKCS1-v1_5 using SHA-512', RS512: t('tools.jwt-parser.RS512'),
ES256: 'ECDSA using P-256 and SHA-256', ES256: t('tools.jwt-parser.ES256'),
ES384: 'ECDSA using P-384 and SHA-384', ES384: t('tools.jwt-parser.ES384'),
ES512: 'ECDSA using P-521 and SHA-512', ES512: t('tools.jwt-parser.ES512'),
PS256: 'RSASSA-PSS using SHA-256 and MGF1 with SHA-256', PS256: t('tools.jwt-parser.PS256'),
PS384: 'RSASSA-PSS using SHA-384 and MGF1 with SHA-384', PS384: t('tools.jwt-parser.PS384'),
PS512: 'RSASSA-PSS using SHA-512 and MGF1 with SHA-512', PS512: t('tools.jwt-parser.PS512'),
none: 'No digital signature or MAC performed', none: t('tools.jwt-parser.none'),
}; };
// List extracted from IANA: https://www.iana.org/assignments/jwt/jwt.xhtml // List extracted from IANA: https://www.iana.org/assignments/jwt/jwt.xhtml
export const CLAIM_DESCRIPTIONS: Record<string, string> = { export const CLAIM_DESCRIPTIONS: Record<string, string> = {
typ: 'Type', typ: t('tools.jwt-parser.typ'),
alg: 'Algorithm', alg: t('tools.jwt-parser.alg'),
iss: 'Issuer', iss: t('tools.jwt-parser.iss'),
sub: 'Subject', sub: t('tools.jwt-parser.sub'),
aud: 'Audience', aud: t('tools.jwt-parser.aud'),
exp: 'Expiration Time', exp: t('tools.jwt-parser.exp'),
nbf: 'Not Before', nbf: t('tools.jwt-parser.nbf'),
iat: 'Issued At', iat: t('tools.jwt-parser.iat'),
jti: 'JWT ID', jti: t('tools.jwt-parser.jti'),
name: 'Full name', name: t('tools.jwt-parser.name'),
given_name: 'Given name(s) or first name(s)', given_name: t('tools.jwt-parser.givenName'),
family_name: 'Surname(s) or last name(s)', family_name: t('tools.jwt-parser.familyName'),
middle_name: 'Middle name(s)', middle_name: t('tools.jwt-parser.middleName'),
nickname: 'Casual name', nickname: t('tools.jwt-parser.nickname'),
preferred_username: 'Shorthand name by which the End-User wishes to be referred to', preferred_username: t('tools.jwt-parser.preferredUsername'),
profile: 'Profile page URL', profile: t('tools.jwt-parser.profile'),
picture: 'Profile picture URL', picture: t('tools.jwt-parser.picture'),
website: 'Web page or blog URL', website: t('tools.jwt-parser.website'),
email: 'Preferred e-mail address', email: t('tools.jwt-parser.email'),
email_verified: 'True if the e-mail address has been verified; otherwise false', email_verified: t('tools.jwt-parser.emailVerified'),
gender: 'Gender', gender: t('tools.jwt-parser.gender'),
birthdate: 'Birthday', birthdate: t('tools.jwt-parser.birthdate'),
zoneinfo: 'Time zone', zoneinfo: t('tools.jwt-parser.zoneinfo'),
locale: 'Locale', locale: t('tools.jwt-parser.locale'),
phone_number: 'Preferred telephone number', phone_number: t('tools.jwt-parser.phoneNumber'),
phone_number_verified: 'True if the phone number has been verified; otherwise false', phone_number_verified: t('tools.jwt-parser.phoneNumberVerified'),
address: 'Preferred postal address', address: t('tools.jwt-parser.address'),
updated_at: 'Time the information was last updated', updated_at: t('tools.jwt-parser.updatedAt'),
azp: 'Authorized party - the party to which the ID Token was issued', azp: t('tools.jwt-parser.azp'),
nonce: 'Value used to associate a Client session with an ID Token', nonce: t('tools.jwt-parser.nonce'),
auth_time: 'Time when the authentication occurred', auth_time: t('tools.jwt-parser.authTime'),
at_hash: 'Access Token hash value', at_hash: t('tools.jwt-parser.atHash'),
c_hash: 'Code hash value', c_hash: t('tools.jwt-parser.cHash'),
acr: 'Authentication Context Class Reference', acr: t('tools.jwt-parser.acr'),
amr: 'Authentication Methods References', amr: t('tools.jwt-parser.amr'),
sub_jwk: 'Public key used to check the signature of an ID Token', sub_jwk: t('tools.jwt-parser.subJwk'),
cnf: 'Confirmation', cnf: t('tools.jwt-parser.cnf'),
sip_from_tag: 'SIP From tag header field parameter value', sip_from_tag: t('tools.jwt-parser.sipFromTag'),
sip_date: 'SIP Date header field value', sip_date: t('tools.jwt-parser.sipDate'),
sip_callid: 'SIP Call-Id header field value', sip_callid: t('tools.jwt-parser.sipCallid'),
sip_cseq_num: 'SIP CSeq numeric header field parameter value', sip_cseq_num: t('tools.jwt-parser.sipCseqNum'),
sip_via_branch: 'SIP Via branch header field parameter value', sip_via_branch: t('tools.jwt-parser.sipViaBranch'),
orig: 'Originating Identity String', orig: t('tools.jwt-parser.orig'),
dest: 'Destination Identity String', dest: t('tools.jwt-parser.dest'),
mky: 'Media Key Fingerprint String', mky: t('tools.jwt-parser.mky'),
events: 'Security Events', events: t('tools.jwt-parser.events'),
toe: 'Time of Event', toe: t('tools.jwt-parser.toe'),
txn: 'Transaction Identifier', txn: t('tools.jwt-parser.txn'),
rph: 'Resource Priority Header Authorization', rph: t('tools.jwt-parser.rph'),
sid: 'Session ID', sid: t('tools.jwt-parser.sid'),
vot: 'Vector of Trust value', vot: t('tools.jwt-parser.vot'),
vtm: 'Vector of Trust trustmark URL', vtm: t('tools.jwt-parser.vtm'),
attest: 'Attestation level as defined in SHAKEN framework', attest: t('tools.jwt-parser.attest'),
origid: 'Originating Identifier as defined in SHAKEN framework', origid: t('tools.jwt-parser.origid'),
act: 'Actor', act: t('tools.jwt-parser.act'),
scope: 'Scope Values', scope: t('tools.jwt-parser.scope'),
client_id: 'Client Identifier', client_id: t('tools.jwt-parser.clientId'),
may_act: 'Authorized Actor - the party that is authorized to become the actor', may_act: t('tools.jwt-parser.mayAct'),
jcard: 'jCard data', jcard: t('tools.jwt-parser.jcard'),
at_use_nbr: 'Number of API requests for which the access token can be used', at_use_nbr: t('tools.jwt-parser.atUseNbr'),
div: 'Diverted Target of a Call', div: t('tools.jwt-parser.div'),
opt: 'Original PASSporT (in Full Form)', opt: t('tools.jwt-parser.opt'),
vc: 'Verifiable Credential as specified in the W3C Recommendation', vc: t('tools.jwt-parser.vc'),
vp: 'Verifiable Presentation as specified in the W3C Recommendation', vp: t('tools.jwt-parser.vp'),
sph: 'SIP Priority header field', sph: t('tools.jwt-parser.sph'),
ace_profile: 'ACE profile a token is supposed to be used with.', ace_profile: t('tools.jwt-parser.aceProfile'),
cnonce: 'Client nonce', cnonce: t('tools.jwt-parser.cnonce'),
exi: 'Expires in', exi: t('tools.jwt-parser.exi'),
roles: 'Roles', roles: t('tools.jwt-parser.roles'),
groups: 'Groups', groups: t('tools.jwt-parser.groups'),
entitlements: 'Entitlements', entitlements: t('tools.jwt-parser.entitlements'),
token_introspection: 'Token introspection response', token_introspection: t('tools.jwt-parser.tokenIntrospection'),
}; };

View file

@ -4,6 +4,8 @@ import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean'; import { isNotThrowing } from '@/utils/boolean';
import { withDefaultOnError } from '@/utils/defaults'; import { withDefaultOnError } from '@/utils/defaults';
const { t } = useI18n();
const rawJwt = ref( const rawJwt = ref(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
); );
@ -13,8 +15,8 @@ const decodedJWT = computed(() =>
); );
const sections = [ const sections = [
{ key: 'header', title: 'Header' }, { key: 'header', title: t('tools.jwt-parser.header') },
{ key: 'payload', title: 'Payload' }, { key: 'payload', title: t('tools.jwt-parser.payload') },
] as const; ] as const;
const validation = useValidation({ const validation = useValidation({
@ -22,7 +24,7 @@ const validation = useValidation({
rules: [ rules: [
{ {
validator: value => value.length > 0 && isNotThrowing(() => decodeJwt({ jwt: rawJwt.value })), validator: value => value.length > 0 && isNotThrowing(() => decodeJwt({ jwt: rawJwt.value })),
message: 'Invalid JWT', message: t('tools.jwt-parser.invalidJWT'),
}, },
], ],
}); });
@ -30,7 +32,7 @@ const validation = useValidation({
<template> <template>
<c-card> <c-card>
<c-input-text v-model:value="rawJwt" label="JWT to decode" :validation="validation" placeholder="Put your token here..." rows="5" multiline raw-text autofocus mb-3 /> <c-input-text v-model:value="rawJwt" :label="t('tools.jwt-parser.label')" :validation="validation" :placeholder="t('tools.jwt-parser.placeholder')" rows="5" multiline raw-text autofocus mb-3 />
<n-table v-if="validation.isValid"> <n-table v-if="validation.isValid">
<tbody> <tbody>

View file

@ -2,3 +2,97 @@ tools:
jwt-parser: jwt-parser:
title: JWT parser title: JWT parser
description: Parse and decode your JSON Web Token (jwt) and display its content. description: Parse and decode your JSON Web Token (jwt) and display its content.
label: JWT to decode
placeholder: Put your token here...
header: Header
payload: Payload
invalidJWT: Invalid JWT
HS256: 'HMAC using SHA-256'
HS384: 'HMAC using SHA-384'
HS512: 'HMAC using SHA-512'
RS256: 'RSASSA-PKCS1-v1_5 using SHA-256'
RS384: 'RSASSA-PKCS1-v1_5 using SHA-384'
RS512: 'RSASSA-PKCS1-v1_5 using SHA-512'
ES256: 'ECDSA using P-256 and SHA-256'
ES384: 'ECDSA using P-384 and SHA-384'
ES512: 'ECDSA using P-521 and SHA-512'
PS256: 'RSASSA-PSS using SHA-256 and MGF1 with SHA-256'
PS384: 'RSASSA-PSS using SHA-384 and MGF1 with SHA-384'
PS512: 'RSASSA-PSS using SHA-512 and MGF1 with SHA-512'
none: 'No digital signature or MAC performed'
typ: 'Type'
alg: 'Algorithm'
iss: 'Issuer'
sub: 'Subject'
aud: 'Audience'
exp: 'Expiration Time'
nbf: 'Not Before'
iat: 'Issued At'
jti: 'JWT ID'
name: 'Full name'
givenName: 'Given name(s) or first name(s)'
familyName: 'Surname(s) or last name(s)'
middleName: 'Middle name(s)'
nickname: 'Casual name'
preferredUsername: 'Shorthand name by which the End-User wishes to be referred to'
profile: 'Profile page URL'
picture: 'Profile picture URL'
website: 'Web page or blog URL'
email: 'Preferred e-mail address'
emailVerified: 'True if the e-mail address has been verified; otherwise false'
gender: 'Gender'
birthdate: 'Birthday'
zoneinfo: 'Time zone'
locale: 'Locale'
phoneNumber: 'Preferred telephone number'
phoneNumberVerified: 'True if the phone number has been verified; otherwise false'
address: 'Preferred postal address'
updatedAt: 'Time the information was last updated'
azp: 'Authorized party - the party to which the ID Token was issued'
nonce: 'Value used to associate a Client session with an ID Token'
authTime: 'Time when the authentication occurred'
atHash: 'Access Token hash value'
cHash: 'Code hash value'
acr: 'Authentication Context Class Reference'
amr: 'Authentication Methods References'
subJwk: 'Public key used to check the signature of an ID Token'
cnf: 'Confirmation'
sipFromTag: 'SIP From tag header field parameter value'
sipDate: 'SIP Date header field value'
sipCallid: 'SIP Call-Id header field value'
sipCseqNum: 'SIP CSeq numeric header field parameter value'
sipViaBranch: 'SIP Via branch header field parameter value'
orig: 'Originating Identity String'
dest: 'Destination Identity String'
mky: 'Media Key Fingerprint String'
events: 'Security Events'
toe: 'Time of Event'
txn: 'Transaction Identifier'
rph: 'Resource Priority Header Authorization'
sid: 'Session ID'
vot: 'Vector of Trust value'
vtm: 'Vector of Trust trustmark URL'
attest: 'Attestation level as defined in SHAKEN framework'
origid: 'Originating Identifier as defined in SHAKEN framework'
act: 'Actor'
scope: 'Scope Values'
clientId: 'Client Identifier'
mayAct: 'Authorized Actor - the party that is authorized to become the actor'
jcard: 'jCard data'
atUseNbr: 'Number of API requests for which the access token can be used'
div: 'Diverted Target of a Call'
opt: 'Original PASSporT (in Full Form)'
vc: 'Verifiable Credential as specified in the W3C Recommendation'
vp: 'Verifiable Presentation as specified in the W3C Recommendation'
sph: 'SIP Priority header field'
aceProfile: 'ACE profile a token is supposed to be used with.'
cnonce: 'Client nonce'
exi: 'Expires in'
roles: 'Roles'
groups: 'Groups'
entitlements: 'Entitlements'
tokenIntrospection: 'Token introspection response'

View file

@ -0,0 +1,98 @@
tools:
jwt-parser:
title: JWT 解析器
description: 解析和解码您的 JSON Web Token (jwt) 并显示其内容。
label: 要解码的 JWT
placeholder: 在这里放置您的令牌...
header: 头部
payload: 负载
invalidJWT: 无效的 JWT
HS256: '使用 SHA-256 的 HMAC'
HS384: '使用 SHA-384 的 HMAC'
HS512: '使用 SHA-512 的 HMAC'
RS256: '使用 SHA-256 的 RSASSA-PKCS1-v1_5'
RS384: '使用 SHA-384 的 RSASSA-PKCS1-v1_5'
RS512: '使用 SHA-512 的 RSASSA-PKCS1-v1_5'
ES256: '使用 P-256 和 SHA-256 的 ECDSA'
ES384: '使用 P-384 和 SHA-384 的 ECDSA'
ES512: '使用 P-521 和 SHA-512 的 ECDSA'
PS256: '使用 SHA-256 和具有 SHA-256 的 MGF1 的 RSASSA-PSS'
PS384: '使用 SHA-384 和具有 SHA-384 的 MGF1 的 RSASSA-PSS'
PS512: '使用 SHA-512 和具有 SHA-512 的 MGF1 的 RSASSA-PSS'
none: '未执行数字签名或 MAC'
typ: '类型'
alg: '算法'
iss: '签发者'
sub: '主题'
aud: '受众'
exp: '过期时间'
nbf: '不早于'
iat: '签发时间'
jti: 'JWT ID'
name: '全名'
givenName: '名字'
familyName: '姓氏'
middleName: '中间名'
nickname: '昵称'
preferredUsername: '用户希望被称呼的简称'
profile: '个人资料页面 URL'
picture: '个人资料图片 URL'
website: '网页或博客 URL'
email: '首选电子邮件地址'
emailVerified: '如果电子邮件地址已经验证为 true否则为 false'
gender: '性别'
birthdate: '生日'
zoneinfo: '时区'
locale: '区域设置'
phoneNumber: '首选电话号码'
phoneNumberVerified: '如果电话号码已经验证为 true否则为 false'
address: '首选邮寄地址'
updatedAt: '信息上次更新时间'
azp: '授权方 - 发出 ID 令牌的一方'
nonce: '用于将客户端会话与 ID 令牌关联的值'
authTime: '认证发生时间'
atHash: '访问令牌哈希值'
cHash: '代码哈希值'
acr: '认证上下文类引用'
amr: '认证方法引用'
subJwk: '用于验证 ID 令牌签名的公钥'
cnf: '确认'
sipFromTag: 'SIP From 标签头字段参数值'
sipDate: 'SIP 日期头字段值'
sipCallid: 'SIP Call-Id 头字段值'
sipCseqNum: 'SIP CSeq 数字头字段参数值'
sipViaBranch: 'SIP Via 分支头字段参数值'
orig: '原始身份字符串'
dest: '目标身份字符串'
mky: '媒体密钥指纹字符串'
events: '安全事件'
toe: '事件时间'
txn: '交易标识符'
rph: '资源优先级头授权'
sid: '会话 ID'
vot: '信任向量值'
vtm: '信任向量标记 URL'
attest: 'SHAKEN 框架中定义的证明级别'
origid: 'SHAKEN 框架中定义的起始标识符'
act: '操作者'
scope: '范围值'
clientId: '客户端标识符'
mayAct: '授权操作者 - 被授权成为操作者的一方'
jcard: 'jCard 数据'
atUseNbr: '访问令牌可用于的 API 请求次数'
div: '呼叫的被转移目标'
opt: '原始 PASSporT完整形式'
vc: '根据 W3C 推荐规范指定的可验证凭证'
vp: '根据 W3C 推荐规范指定的可验证展示'
sph: 'SIP 优先级头字段'
aceProfile: 'ACE 配置文件应与之一起使用的令牌'
cnonce: '客户端随机数'
exi: '剩余时间'
roles: '角色'
groups: '组'
entitlements: '权利'
tokenIntrospection: '令牌内省响应'

View file

@ -1,10 +1,11 @@
import { Keyboard } from '@vicons/tabler'; import { Keyboard } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'Keycode info', name: t('tools.keycode-info.title'),
path: '/keycode-info', path: '/keycode-info',
description: 'Find the javascript keycode, code, location and modifiers of any pressed key.', description: t('tools.keycode-info.description'),
keywords: [ keywords: [
'keycode', 'keycode',
'info', 'info',

View file

@ -5,6 +5,8 @@ import InputCopyable from '../../components/InputCopyable.vue';
const event = ref<KeyboardEvent>(); const event = ref<KeyboardEvent>();
const { t } = useI18n();
useEventListener(document, 'keydown', (e) => { useEventListener(document, 'keydown', (e) => {
event.value = e; event.value = e;
}); });
@ -16,28 +18,28 @@ const fields = computed(() => {
return [ return [
{ {
label: 'Key :', label: t('tools.keycode-info.keyLabel'),
value: event.value.key, value: event.value.key,
placeholder: 'Key name...', placeholder: t('tools.keycode-info.keyPlaceholder'),
}, },
{ {
label: 'Keycode :', label: t('tools.keycode-info.keycodeLabel'),
value: String(event.value.keyCode), value: String(event.value.keyCode),
placeholder: 'Keycode...', placeholder: t('tools.keycode-info.keycodePlaceholder'),
}, },
{ {
label: 'Code :', label: t('tools.keycode-info.codeLabel'),
value: event.value.code, value: event.value.code,
placeholder: 'Code...', placeholder: t('tools.keycode-info.codePlaceholder'),
}, },
{ {
label: 'Location :', label: t('tools.keycode-info.locationLabel'),
value: String(event.value.location), value: String(event.value.location),
placeholder: 'Code...', placeholder: t('tools.keycode-info.locationPlaceholder'),
}, },
{ {
label: 'Modifiers :', label: t('tools.keycode-info.modifiersLabel'),
value: [ value: [
event.value.metaKey && 'Meta', event.value.metaKey && 'Meta',
event.value.shiftKey && 'Shift', event.value.shiftKey && 'Shift',
@ -46,7 +48,7 @@ const fields = computed(() => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' + '), .join(' + '),
placeholder: 'None', placeholder: t('tools.keycode-info.modifiersPlaceholder'),
}, },
]; ];
}); });
@ -59,7 +61,7 @@ const fields = computed(() => {
{{ event.key }} {{ event.key }}
</div> </div>
<span lh-1 op-70> <span lh-1 op-70>
Press the key on your keyboard you want to get info about this key {{ t('tools.keycode-info.tips') }}
</span> </span>
</c-card> </c-card>

View file

@ -2,3 +2,16 @@ tools:
keycode-info: keycode-info:
title: Keycode info title: Keycode info
description: Find the javascript keycode, code, location and modifiers of any pressed key. description: Find the javascript keycode, code, location and modifiers of any pressed key.
keyLabel: 'Key :'
keyPlaceholder: 'Key name...'
keycodeLabel: 'Keycode :'
keycodePlaceholder: 'Keycode...'
codeLabel: 'Code :'
codePlaceholder: 'Code...'
locationLabel: 'Location :'
locationPlaceholder: 'Code...'
modifiersLabel: 'Modifiers :'
modifiersPlaceholder: 'None'
tips: Press the key on your keyboard you want to get info about this key

View file

@ -0,0 +1,17 @@
tools:
keycode-info:
title: 按键信息
description: 查找任何按键的 JavaScript 按键码、代码、位置和修饰键。
keyLabel: '按键:'
keyPlaceholder: '按键名称...'
keycodeLabel: '按键码:'
keycodePlaceholder: '按键码...'
codeLabel: '代码:'
codePlaceholder: '代码...'
locationLabel: '位置:'
locationPlaceholder: '位置...'
modifiersLabel: '修饰键:'
modifiersPlaceholder: '无'
tips: 按下您键盘上想要获取信息的按键

View file

@ -1,10 +1,11 @@
import { AbcRound } from '@vicons/material'; import { AbcRound } from '@vicons/material';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'Slugify string', name: t('tools.slugify-string.title'),
path: '/slugify-string', path: '/slugify-string',
description: 'Make a string url, filename and id safe.', description: t('tools.slugify-string.description'),
keywords: ['slugify', 'string', 'escape', 'emoji', 'special', 'character', 'space', 'trim'], keywords: ['slugify', 'string', 'escape', 'emoji', 'special', 'character', 'space', 'trim'],
component: () => import('./slugify-string.vue'), component: () => import('./slugify-string.vue'),
icon: AbcRound, icon: AbcRound,

View file

@ -2,3 +2,11 @@ tools:
slugify-string: slugify-string:
title: Slugify string title: Slugify string
description: Make a string url, filename and id safe. description: Make a string url, filename and id safe.
inputLabel: Your string to slugify
inputPlaceholder: 'Put your string here (ex: My file path)'
outputLabel: Your slug
outputPlaceholder: 'You slug will be generated here (ex: my-file-path)'
copyBtn: Copy slug
copied: Slug copied to clipboard

View file

@ -0,0 +1,12 @@
tools:
slugify-string:
title: 字符串转换为 Slug
description: 将字符串转换为 URL、文件名和 ID 安全的格式。
inputLabel: 要转换为 Slug 的字符串
inputPlaceholder: '在此输入您的字符串(例如:我的文件路径)'
outputLabel: 您的 Slug
outputPlaceholder: '您的 Slug 将在此生成例如my-file-path'
copyBtn: 复制 Slug
copied: Slug 已复制到剪贴板

View file

@ -3,20 +3,21 @@ import slugify from '@sindresorhus/slugify';
import { withDefaultOnError } from '@/utils/defaults'; import { withDefaultOnError } from '@/utils/defaults';
import { useCopy } from '@/composable/copy'; import { useCopy } from '@/composable/copy';
const { t } = useI18n();
const input = ref(''); const input = ref('');
const slug = computed(() => withDefaultOnError(() => slugify(input.value), '')); const slug = computed(() => withDefaultOnError(() => slugify(input.value), ''));
const { copy } = useCopy({ source: slug, text: 'Slug copied to clipboard' }); const { copy } = useCopy({ source: slug, text: t('tools.slugify-string.copied') });
</script> </script>
<template> <template>
<div> <div>
<c-input-text v-model:value="input" multiline placeholder="Put your string here (ex: My file path)" label="Your string to slugify" autofocus raw-text mb-5 /> <c-input-text v-model:value="input" multiline :placeholder="t('tools.slugify-string.inputPlaceholder')" :label="t('tools.slugify-string.inputLabel')" autofocus raw-text mb-5 />
<c-input-text :value="slug" multiline readonly placeholder="You slug will be generated here (ex: my-file-path)" label="Your slug" mb-5 /> <c-input-text :value="slug" multiline readonly :placeholder="t('tools.slugify-string.outputPlaceholder')" :label="t('tools.slugify-string.outputLabel')" mb-5 />
<div flex justify-center> <div flex justify-center>
<c-button :disabled="slug.length === 0" @click="copy()"> <c-button :disabled="slug.length === 0" @click="copy()">
Copy slug {{ t('tools.slugify-string.copyBtn') }}
</c-button> </c-button>
</div> </div>
</div> </div>

View file

@ -1,10 +1,11 @@
import { Browser } from '@vicons/tabler'; import { Browser } from '@vicons/tabler';
import { defineTool } from '../tool'; import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({ export const tool = defineTool({
name: 'User-agent parser', name: t('tools.user-agent-parser.title'),
path: '/user-agent-parser', path: '/user-agent-parser',
description: 'Detect and parse Browser, Engine, OS, CPU, and Device type/model from an user-agent string.', description: t('tools.user-agent-parser.description'),
keywords: ['user', 'agent', 'parser', 'browser', 'engine', 'os', 'cpu', 'device', 'user-agent', 'client'], keywords: ['user', 'agent', 'parser', 'browser', 'engine', 'os', 'cpu', 'device', 'user-agent', 'client'],
component: () => import('./user-agent-parser.vue'), component: () => import('./user-agent-parser.vue'),
icon: Browser, icon: Browser,

View file

@ -2,3 +2,29 @@ tools:
user-agent-parser: user-agent-parser:
title: User-agent parser title: User-agent parser
description: Detect and parse Browser, Engine, OS, CPU, and Device type/model from an user-agent string. description: Detect and parse Browser, Engine, OS, CPU, and Device type/model from an user-agent string.
name: Name
version: Version
model: Model
type: Type
vendor: Vendor
architecture: Architecture
browser: Browser
browserNameFallback: No browser name available
browserVersionFallback: No browser version available
engine: Engine
engineNameFallback: No engine name available
engineVersionFallback: No engine version available
os: OS
osNameFallback: No OS name available
osVersionFallback: No OS version available
device: Device
deviceModelFallback: No device model available
deviceTypeFallback: No device type available
deviceVendorFallback: No device vendor available
cpu: CPU
cpuArchitectureFallback: No CPU architecture available
label: User agent string
placeholder: Put your user-agent here...

View file

@ -0,0 +1,30 @@
tools:
user-agent-parser:
title: User-agent 解析器
description: 从用户代理字符串中检测和解析浏览器、引擎、操作系统、CPU 和设备类型/型号。
name: 名称
version: 版本
model: 型号
type: 类型
vendor: 供应商
architecture: 架构
browser: 浏览器
browserNameFallback: 没有可用的浏览器名称
browserVersionFallback: 没有可用的浏览器版本
engine: 引擎
engineNameFallback: 没有可用的引擎名称
engineVersionFallback: 没有可用的引擎版本
os: 操作系统
osNameFallback: 没有可用的操作系统名称
osVersionFallback: 没有可用的操作系统版本
device: 设备
deviceModelFallback: 没有可用的设备型号
deviceTypeFallback: 没有可用的设备类型
deviceVendorFallback: 没有可用的设备供应商
cpu: 中央处理器
cpuArchitectureFallback: 没有可用的 CPU 架构
label: 用户代理字符串
placeholder: 在此处输入您的用户代理字符串...

View file

@ -6,6 +6,7 @@ import type { UserAgentResultSection } from './user-agent-parser.types';
import { withDefaultOnError } from '@/utils/defaults'; import { withDefaultOnError } from '@/utils/defaults';
const ua = ref(navigator.userAgent as string); const ua = ref(navigator.userAgent as string);
const { t } = useI18n();
// If not input in the ua field is present return an empty object of type UAParser.IResult because otherwise // If not input in the ua field is present return an empty object of type UAParser.IResult because otherwise
// UAParser returns the values for the current Browser. This is confusing because results are shown for an empty // UAParser returns the values for the current Browser. This is confusing because results are shown for an empty
@ -19,82 +20,82 @@ const userAgentInfo = computed(() => withDefaultOnError(() => getUserAgentInfo(u
const sections: UserAgentResultSection[] = [ const sections: UserAgentResultSection[] = [
{ {
heading: 'Browser', heading: t('tools.user-agent-parser.browser'),
icon: Browser, icon: Browser,
content: [ content: [
{ {
label: 'Name', label: t('tools.user-agent-parser.name'),
getValue: block => block?.browser.name, getValue: block => block?.browser.name,
undefinedFallback: 'No browser name available', undefinedFallback: t('tools.user-agent-parser.browserNameFallback'),
}, },
{ {
label: 'Version', label: t('tools.user-agent-parser.version'),
getValue: block => block?.browser.version, getValue: block => block?.browser.version,
undefinedFallback: 'No browser version available', undefinedFallback: t('tools.user-agent-parser.browserVersionFallback'),
}, },
], ],
}, },
{ {
heading: 'Engine', heading: t('tools.user-agent-parser.engine'),
icon: Engine, icon: Engine,
content: [ content: [
{ {
label: 'Name', label: t('tools.user-agent-parser.name'),
getValue: block => block?.engine.name, getValue: block => block?.engine.name,
undefinedFallback: 'No engine name available', undefinedFallback: t('tools.user-agent-parser.engineNameFallback'),
}, },
{ {
label: 'Version', label: t('tools.user-agent-parser.version'),
getValue: block => block?.engine.version, getValue: block => block?.engine.version,
undefinedFallback: 'No engine version available', undefinedFallback: t('tools.user-agent-parser.engineVersionFallback'),
}, },
], ],
}, },
{ {
heading: 'OS', heading: t('tools.user-agent-parser.os'),
icon: Adjustments, icon: Adjustments,
content: [ content: [
{ {
label: 'Name', label: t('tools.user-agent-parser.name'),
getValue: block => block?.os.name, getValue: block => block?.os.name,
undefinedFallback: 'No OS name available', undefinedFallback: t('tools.user-agent-parser.osNameFallback'),
}, },
{ {
label: 'Version', label: t('tools.user-agent-parser.version'),
getValue: block => block?.os.version, getValue: block => block?.os.version,
undefinedFallback: 'No OS version available', undefinedFallback: t('tools.user-agent-parser.osVersionFallback'),
}, },
], ],
}, },
{ {
heading: 'Device', heading: t('tools.user-agent-parser.device'),
icon: Devices, icon: Devices,
content: [ content: [
{ {
label: 'Model', label: t('tools.user-agent-parser.model'),
getValue: block => block?.device.model, getValue: block => block?.device.model,
undefinedFallback: 'No device model available', undefinedFallback: t('tools.user-agent-parser.deviceModelFallback'),
}, },
{ {
label: 'Type', label: t('tools.user-agent-parser.type'),
getValue: block => block?.device.type, getValue: block => block?.device.type,
undefinedFallback: 'No device type available', undefinedFallback: t('tools.user-agent-parser.deviceTypeFallback'),
}, },
{ {
label: 'Vendor', label: t('tools.user-agent-parser.vendor'),
getValue: block => block?.device.vendor, getValue: block => block?.device.vendor,
undefinedFallback: 'No device vendor available', undefinedFallback: t('tools.user-agent-parser.deviceVendorFallback'),
}, },
], ],
}, },
{ {
heading: 'CPU', heading: t('tools.user-agent-parser.cpu'),
icon: Cpu, icon: Cpu,
content: [ content: [
{ {
label: 'Architecture', label: t('tools.user-agent-parser.architecture'),
getValue: block => block?.cpu.architecture, getValue: block => block?.cpu.architecture,
undefinedFallback: 'No CPU architecture available', undefinedFallback: t('tools.user-agent-parser.cpuArchitectureFallback'),
}, },
], ],
}, },
@ -105,9 +106,9 @@ const sections: UserAgentResultSection[] = [
<div> <div>
<c-input-text <c-input-text
v-model:value="ua" v-model:value="ua"
label="User agent string" :label="t('tools.user-agent-parser.label')"
multiline multiline
placeholder="Put your user-agent here..." :placeholder="t('tools.user-agent-parser.placeholder')"
clearable clearable
raw-text raw-text
rows="2" rows="2"