WIP(translate): translate two category all tools

This commit is contained in:
Amery2010 2024-02-19 17:38:30 +08:00
parent 9db4b41daf
commit 4f550a9499
58 changed files with 708 additions and 172 deletions

1
components.d.ts vendored
View file

@ -80,6 +80,7 @@ declare module '@vue/runtime-core' {
FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default']
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
'GitMemo.content.zh': typeof import('./src/tools/git-memo/git-memo.content.zh.md')['default']
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
'Home.page': typeof import('./src/pages/Home.page.vue')['default']

View file

@ -19,6 +19,8 @@ const {
},
});
const { t } = useI18n();
const video = ref<HTMLVideoElement>();
const medias = ref<Media[]>([]);
const currentCamera = ref(cameras.value[0]?.deviceId);
@ -106,20 +108,19 @@ function downloadMedia({ type, value, createdAt }: Media) {
<template>
<div>
<c-card v-if="!isSupported">
Your browser does not support recording video from camera
{{ t('tools.camera-recorder.unSupported') }}
</c-card>
<c-card v-else-if="!permissionGranted" text-center>
You need to grant permission to use your camera and microphone
{{ t('tools.camera-recorder.needPermissionGranted') }}
<c-alert v-if="permissionCannotBePrompted" mt-4 text-left>
Your browser has blocked permission request or does not support it. You need to grant permission manually in
your browser settings (usually the lock icon in the address bar).
{{ t('tools.camera-recorder.permissionCannotBePrompted') }}
</c-alert>
<div v-else mt-4 flex justify-center>
<c-button @click="requestPermissions">
Grant permission
{{ t('tools.camera-recorder.grantPermission') }}
</c-button>
</div>
</c-card>
@ -130,24 +131,24 @@ function downloadMedia({ type, value, createdAt }: Media) {
v-model:value="currentCamera"
label-position="left"
label-width="60px"
label="Video:"
:label="t('tools.camera-recorder.cameraLabel')"
:options="cameras.map(({ deviceId, label }) => ({ value: deviceId, label }))"
placeholder="Select camera"
:placeholder="t('tools.camera-recorder.cameraPlaceholder')"
/>
<c-select
v-if="currentMicrophone && microphones.length > 0"
v-model:value="currentMicrophone"
label="Audio:"
:label="t('tools.camera-recorder.microphoneLabel')"
label-position="left"
label-width="60px"
:options="microphones.map(({ deviceId, label }) => ({ value: deviceId, label }))"
placeholder="Select microphone"
:placeholder="t('tools.camera-recorder.microphonePlaceholder')"
/>
</div>
<div v-if="!isMediaStreamAvailable" mt-3 flex justify-center>
<c-button type="primary" @click="start">
Start webcam
{{ t('tools.camera-recorder.startWebcam') }}
</c-button>
</div>
@ -159,32 +160,32 @@ function downloadMedia({ type, value, createdAt }: Media) {
<div flex items-center justify-between gap-2>
<c-button :disabled="!isMediaStreamAvailable" @click="takeScreenshot">
<span mr-2> <icon-mdi-camera /></span>
Take screenshot
{{ t('tools.camera-recorder.takeScreenshot') }}
</c-button>
<div v-if="isRecordingSupported" flex justify-center gap-2>
<c-button v-if="recordingState === 'stopped'" @click="startRecording">
<span mr-2> <icon-mdi-video /></span>
Start recording
{{ t('tools.camera-recorder.startRecording') }}
</c-button>
<c-button v-if="recordingState === 'recording'" @click="pauseRecording">
<span mr-2> <icon-mdi-pause /></span>
Pause
{{ t('tools.camera-recorder.pause') }}
</c-button>
<c-button v-if="recordingState === 'paused'" @click="resumeRecording">
<span mr-2> <icon-mdi-play /></span>
Resume
{{ t('tools.camera-recorder.resume') }}
</c-button>
<c-button v-if="recordingState !== 'stopped'" type="error" @click="stopRecording">
<span mr-2> <icon-mdi-record /></span>
Stop
{{ t('tools.camera-recorder.stop') }}
</c-button>
</div>
<div v-else italic op-60>
Video recording is not supported in your browser
{{ t('tools.camera-recorder.unSupportRecord') }}
</div>
</div>
</div>
@ -192,13 +193,13 @@ function downloadMedia({ type, value, createdAt }: Media) {
<div grid grid-cols-2 mt-5 gap-2>
<c-card v-for="({ type, value, createdAt }, index) in medias" :key="index">
<img v-if="type === 'image'" :src="value" max-h-full w-full alt="screenshot">
<img v-if="type === 'image'" :src="value" max-h-full w-full :alt="t('tools.camera-recorder.screenshot')">
<video v-else :src="value" controls max-h-full w-full />
<div flex items-center justify-between>
<div font-bold>
{{ type === 'image' ? 'Screenshot' : 'Video' }}
{{ type === 'image' ? t('tools.camera-recorder.screenshot') : t('tools.camera-recorder.video') }}
</div>
<div flex gap-2>

View file

@ -1,10 +1,11 @@
import { Camera } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Camera recorder',
name: t('tools.camera-recorder.title'),
path: '/camera-recorder',
description: 'Take a picture or record a video from your webcam or camera.',
description: t('tools.camera-recorder.description'),
keywords: ['camera', 'recoder'],
component: () => import('./camera-recorder.vue'),
icon: Camera,

View file

@ -0,0 +1,22 @@
tools:
camera-recorder:
title: Camera recorder
description: Take a picture or record a video from your webcam or camera.
unSupported: Your browser does not support recording video from camera
needPermissionGranted: You need to grant permission to use your camera and microphone
permissionCannotBePrompted: Your browser has blocked permission request or does not support it. You need to grant permission manually in your browser settings (usually the lock icon in the address bar).
grantPermission: Grant permission
cameraLabel: 'Video:'
cameraPlaceholder: Select camera
microphoneLabel: 'Audio:'
microphonePlaceholder: Select microphone
startWebcam: Start webcam
takeScreenshot: Take screenshot
startRecording: Start recording
pause: Pause
resume: Resume
stop: Stop
unSupportRecord: Video recording is not supported in your browser
screenshot: screenshot
video: Video

View file

@ -0,0 +1,22 @@
tools:
camera-recorder:
title: 摄像头录制器
description: 从您的网络摄像头或相机拍照或录制视频。
unSupported: 您的浏览器不支持从摄像头录制视频
needPermissionGranted: 您需要授予使用摄像头和麦克风的权限
permissionCannotBePrompted: 您的浏览器已阻止权限请求或不支持它。 您需要在浏览器设置中手动授予权限(通常在地址栏中的锁图标)。
grantPermission: 授予权限
cameraLabel: '视频:'
cameraPlaceholder: 选择摄像头
microphoneLabel: '音频:'
microphonePlaceholder: 选择麦克风
startWebcam: 启动网络摄像头
takeScreenshot: 拍照
startRecording: 开始录制
pause: 暂停
resume: 恢复
stop: 停止
unSupportRecord: 您的浏览器不支持视频录制
screenshot: 截图
video: 视频

View file

@ -7,11 +7,12 @@ import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation } f
import type { Group, Scope } from './chmod-calculator.types';
const themeVars = useThemeVars();
const { t } = useI18n();
const scopes: { scope: Scope; title: string }[] = [
{ scope: 'read', title: 'Read (4)' },
{ scope: 'write', title: 'Write (2)' },
{ scope: 'execute', title: 'Execute (1)' },
{ scope: 'read', title: t('tools.chmod-calculator.read') },
{ scope: 'write', title: t('tools.chmod-calculator.write') },
{ scope: 'execute', title: t('tools.chmod-calculator.execute') },
];
const groups: Group[] = ['owner', 'group', 'public'];
@ -32,13 +33,13 @@ const symbolic = computed(() => computeChmodSymbolicRepresentation({ permissions
<tr>
<th class="text-center" scope="col" />
<th class="text-center" scope="col">
Owner (u)
{{ t('tools.chmod-calculator.owner') }}
</th>
<th class="text-center" scope="col">
Group (g)
{{ t('tools.chmod-calculator.group') }}
</th>
<th class="text-center" scope="col">
Public (o)
{{ t('tools.chmod-calculator.public') }}
</th>
</tr>
</thead>

View file

@ -1,10 +1,11 @@
import { FileInvoice } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Chmod calculator',
name: t('tools.chmod-calculator.title'),
path: '/chmod-calculator',
description: 'Compute your chmod permissions and commands with this online chmod calculator.',
description: t('tools.chmod-calculator.description'),
keywords: [
'chmod',
'calculator',

View file

@ -0,0 +1,11 @@
tools:
chmod-calculator:
title: Chmod calculator
description: Compute your chmod permissions and commands with this online chmod calculator.
read: 'Read (4)'
write: 'Write (2)'
execute: 'Execute (1)'
owner: 'Owner (u)'
group: 'Group (g)'
public: Public (o)

View file

@ -0,0 +1,11 @@
tools:
chmod-calculator:
title: Chmod 计算器
description: 使用此在线 Chmod 计算器计算您的 Chmod 权限和命令。
read: '读取 (4)'
write: '写入 (2)'
execute: '执行 (1)'
owner: '所有者 (u)'
group: '群组 (g)'
public: 公共 (o)

View file

@ -1,5 +1,7 @@
<script setup lang="ts">
import cronstrue from 'cronstrue';
import 'cronstrue/locales/zh_CN';
import 'cronstrue/locales/fr';
import { isValidCron } from 'cron-validator';
import { useStyleStore } from '@/stores/style.store';
@ -9,84 +11,86 @@ function isCronValid(v: string) {
const styleStore = useStyleStore();
const { t, locale } = useI18n();
const cron = ref('40 * * * *');
const cronstrueConfig = reactive({
verbose: true,
dayOfWeekStartIndexZero: true,
use24HourTimeFormat: true,
throwExceptionOnParseError: true,
locale: locale.value === 'zh' ? 'zh_CN' : locale,
});
const helpers = [
{
symbol: '*',
meaning: 'Any value',
meaning: t('tools.crontab-generator.anyMeaning'),
example: '* * * *',
equivalent: 'Every minute',
equivalent: t('tools.crontab-generator.anyExample'),
},
{
symbol: '-',
meaning: 'Range of values',
meaning: t('tools.crontab-generator.rangeMeaning'),
example: '1-10 * * *',
equivalent: 'Minutes 1 through 10',
equivalent: t('tools.crontab-generator.rangeExample'),
},
{
symbol: ',',
meaning: 'List of values',
meaning: t('tools.crontab-generator.listMeaning'),
example: '1,10 * * *',
equivalent: 'At minutes 1 and 10',
equivalent: t('tools.crontab-generator.listExample'),
},
{
symbol: '/',
meaning: 'Step values',
meaning: t('tools.crontab-generator.stepMeaning'),
example: '*/10 * * *',
equivalent: 'Every 10 minutes',
equivalent: t('tools.crontab-generator.stepExample'),
},
{
symbol: '@yearly',
meaning: 'Once every year at midnight of 1 January',
meaning: t('tools.crontab-generator.yearlyMeaning'),
example: '@yearly',
equivalent: '0 0 1 1 *',
},
{
symbol: '@annually',
meaning: 'Same as @yearly',
meaning: t('tools.crontab-generator.annuallyMeaning', { yearly: '@yearly' }),
example: '@annually',
equivalent: '0 0 1 1 *',
},
{
symbol: '@monthly',
meaning: 'Once a month at midnight on the first day',
meaning: t('tools.crontab-generator.monthlyMeaning'),
example: '@monthly',
equivalent: '0 0 1 * *',
},
{
symbol: '@weekly',
meaning: 'Once a week at midnight on Sunday morning',
meaning: t('tools.crontab-generator.weeklyMeaning'),
example: '@weekly',
equivalent: '0 0 * * 0',
},
{
symbol: '@daily',
meaning: 'Once a day at midnight',
meaning: t('tools.crontab-generator.dailyMeaning'),
example: '@daily',
equivalent: '0 0 * * *',
},
{
symbol: '@midnight',
meaning: 'Same as @daily',
meaning: t('tools.crontab-generator.midnightMeaning', { daily: '@daily' }),
example: '@midnight',
equivalent: '0 0 * * *',
},
{
symbol: '@hourly',
meaning: 'Once an hour at the beginning of the hour',
meaning: t('tools.crontab-generator.hourlyMeaning'),
example: '@hourly',
equivalent: '0 * * * *',
},
{
symbol: '@reboot',
meaning: 'Run at startup',
meaning: t('tools.crontab-generator.rebootMeaning'),
example: '',
equivalent: '',
},
@ -102,7 +106,7 @@ const cronString = computed(() => {
const cronValidationRules = [
{
validator: (value: string) => isCronValid(value),
message: 'This cron is invalid',
message: t('tools.crontab-generator.invalidMessage'),
},
];
</script>
@ -127,13 +131,13 @@ const cronValidationRules = [
<div flex justify-center>
<n-form :show-feedback="false" label-width="170" label-placement="left">
<n-form-item label="Verbose">
<n-form-item :label="t('tools.crontab-generator.verbose')">
<n-switch v-model:value="cronstrueConfig.verbose" />
</n-form-item>
<n-form-item label="Use 24 hour time format">
<n-form-item :label="t('tools.crontab-generator.use24HourTimeFormat')">
<n-switch v-model:value="cronstrueConfig.use24HourTimeFormat" />
</n-form-item>
<n-form-item label="Days start at 0">
<n-form-item :label="t('tools.crontab-generator.dayOfWeekStartIndexZero')">
<n-switch v-model:value="cronstrueConfig.dayOfWeekStartIndexZero" />
</n-form-item>
</n-form>
@ -141,29 +145,29 @@ const cronValidationRules = [
</c-card>
<c-card>
<pre>
[optional] seconds (0 - 59)
| minute (0 - 59)
| | hour (0 - 23)
| | | day of month (1 - 31)
| | | | month (1 - 12) OR jan,feb,mar,apr ...
| | | | | day of week (0 - 6, sunday=0) OR sun,mon ...
{{ t('tools.crontab-generator.secondDesc') }}
| {{ t('tools.crontab-generator.minuteDesc') }}
| | {{ t('tools.crontab-generator.hourDesc') }}
| | | {{ t('tools.crontab-generator.dayOfMonthDesc') }}
| | | | {{ t('tools.crontab-generator.monthDesc') }}
| | | | | {{ t('tools.crontab-generator.dayOfWeekDesc') }}
| | | | | |
* * * * * * command</pre>
* * * * * * {{ t('tools.crontab-generator.command') }}</pre>
<div v-if="styleStore.isSmallScreen">
<c-card v-for="{ symbol, meaning, example, equivalent } in helpers" :key="symbol" mb-3 important:border-none>
<div>
Symbol: <strong>{{ symbol }}</strong>
{{ t('tools.crontab-generator.symbol') }} <strong>{{ symbol }}</strong>
</div>
<div>
Meaning: <strong>{{ meaning }}</strong>
{{ t('tools.crontab-generator.meaning') }} <strong>{{ meaning }}</strong>
</div>
<div>
Example:
{{ t('tools.crontab-generator.example') }}
<strong><code>{{ example }}</code></strong>
</div>
<div>
Equivalent: <strong>{{ equivalent }}</strong>
{{ t('tools.crontab-generator.equivalent') }} <strong>{{ equivalent }}</strong>
</div>
</c-card>
</div>

View file

@ -1,10 +1,11 @@
import { Alarm } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Crontab generator',
name: t('tools.crontab-generator.title'),
path: '/crontab-generator',
description: 'Validate and generate crontab and get the human readable description of the cron schedule.',
description: t('tools.crontab-generator.description'),
keywords: [
'crontab',
'generator',

View file

@ -0,0 +1,38 @@
tools:
crontab-generator:
title: Crontab generator
description: Validate and generate crontab and get the human readable description of the cron schedule.
anyMeaning: Any value
anyExample: Every minute
rangeMeaning: Range of values
rangeExample: Minutes 1 through 10
listMeaning: List of values
listExample: At minutes 1 and 10
stepMeaning: Step values
stepExample: Every 10 minutes
yearlyMeaning: Once every year at midnight of 1 January
annuallyMeaning: Same as {yearly}
monthlyMeaning: Once a month at midnight on the first day
weeklyMeaning: Once a week at midnight on Sunday morning
dailyMeaning: Once a day at midnight
midnightMeaning: Same as {daily}
hourlyMeaning: Once an hour at the beginning of the hour
rebootMeaning: Run at startup
verbose: Verbose
use24HourTimeFormat: Use 24 hour time format
dayOfWeekStartIndexZero: Days start at 0
secondDesc: '[optional] seconds (0 - 59)'
minuteDesc: 'minute (0 - 59)'
hourDesc: 'hour (0 - 23)'
dayOfMonthDesc: 'day of month (1 - 31)'
monthDesc: 'month (1 - 12) OR jan,feb,mar,apr ...'
dayOfWeekDesc: 'day of week (0 - 6, sunday=0) OR sun,mon ...'
command: command
symbol: 'Symbol:'
meaning: 'Meaning:'
example: 'Example:'
equivalent: 'Equivalent:'
invalidMessage: This cron is invalid

View file

@ -0,0 +1,38 @@
tools:
crontab-generator:
title: 定时任务生成器
description: 验证和生成 crontab并获取定时任务的可读描述。
anyMeaning: 任意值
anyExample: 每分钟
rangeMeaning: 值范围
rangeExample: 1 到 10 分钟
listMeaning: 值列表
listExample: 在第 1 和第 10 分钟
stepMeaning: 步进值
stepExample: 每 10 分钟
yearlyMeaning: 每年 1 月 1 日午夜
annuallyMeaning: '等同于 {yearly}'
monthlyMeaning: 每月第一天午夜
weeklyMeaning: 每周星期日凌晨
dailyMeaning: 每天午夜
midnightMeaning: '等同于 {daily}'
hourlyMeaning: 每小时整点
rebootMeaning: 启动时运行
verbose: 冗长模式
use24HourTimeFormat: 使用 24 小时时间格式
dayOfWeekStartIndexZero: 一周从 0 开始
secondDesc: '[可选] 秒0 - 59'
minuteDesc: '分钟0 - 59'
hourDesc: '小时0 - 23'
dayOfMonthDesc: '日期1 - 31'
monthDesc: '月份1 - 12或 jan、feb、mar、apr ...'
dayOfWeekDesc: '星期几0 - 6星期日=0或 sun、mon ...'
command: 命令
symbol: '符号:'
meaning: '含义:'
example: '示例:'
equivalent: '等同:'
invalidMessage: 此 cron 表达式无效

View file

@ -5,6 +5,7 @@ import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
import { textToBase64 } from '@/utils/base64';
import TextareaCopyable from '@/components/TextareaCopyable.vue';
const { t } = useI18n();
const dockerRun = ref(
'docker run -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro --restart always --log-opt max-size=1g nginx',
);
@ -32,12 +33,12 @@ const { download } = useDownloadFileFromBase64({ source: dockerComposeBase64, fi
<div>
<c-input-text
v-model:value="dockerRun"
label="Your docker run command:"
:label="t('tools.docker-run-to-docker-compose-converter.inputLabel')"
style="font-family: monospace"
multiline
raw-text
monospace
placeholder="Your docker run command to convert..."
:placeholder="t('tools.docker-run-to-docker-compose-converter.inputPlaceholder')"
rows="3"
/>
@ -47,12 +48,12 @@ const { download } = useDownloadFileFromBase64({ source: dockerComposeBase64, fi
<div mt-5 flex justify-center>
<c-button :disabled="dockerCompose === ''" secondary @click="download">
Download docker-compose.yml
{{ t('tools.docker-run-to-docker-compose-converter.downloadBtn') }}
</c-button>
</div>
<div v-if="notComposable.length > 0">
<n-alert title="This options are not translatable to docker-compose" type="info" mt-5>
<n-alert :title="t('tools.docker-run-to-docker-compose-converter.notComposable')" type="info" mt-5>
<ul>
<li v-for="(message, index) of notComposable" :key="index">
{{ message }}
@ -63,7 +64,7 @@ const { download } = useDownloadFileFromBase64({ source: dockerComposeBase64, fi
<div v-if="notImplemented.length > 0">
<n-alert
title="This options are not yet implemented and therefore haven't been translated to docker-compose"
:title="t('tools.docker-run-to-docker-compose-converter.notImplemented')"
type="warning"
mt-5
>
@ -76,7 +77,7 @@ const { download } = useDownloadFileFromBase64({ source: dockerComposeBase64, fi
</div>
<div v-if="errors.length > 0">
<n-alert title="The following errors occured" type="error" mt-5>
<n-alert :title="t('tools.docker-run-to-docker-compose-converter.errors')" type="error" mt-5>
<ul>
<li v-for="(message, index) of errors" :key="index">
{{ message }}

View file

@ -1,10 +1,11 @@
import { BrandDocker } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Docker run to Docker compose converter',
name: t('tools.docker-run-to-docker-compose-converter.title'),
path: '/docker-run-to-docker-compose-converter',
description: 'Turns docker run commands into docker-compose files!',
description: t('tools.docker-run-to-docker-compose-converter.description'),
keywords: ['docker', 'run', 'compose', 'yaml', 'yml', 'convert', 'deamon'],
component: () => import('./docker-run-to-docker-compose-converter.vue'),
icon: BrandDocker,

View file

@ -0,0 +1,12 @@
tools:
docker-run-to-docker-compose-converter:
title: Docker run to Docker compose converter
description: Turns docker run commands into docker-compose files!
inputLabel: 'Your docker run command:'
inputPlaceholder: Your docker run command to convert...
downloadBtn: Download docker-compose.yml
notComposable: This options are not translatable to docker-compose
notImplemented: This options are not yet implemented and therefore haven't been translated to docker-compose
errors: The following errors occured

View file

@ -0,0 +1,12 @@
tools:
docker-run-to-docker-compose-converter:
title: Docker run 转换为 Docker compose 转换器
description: 将 docker run 命令转换为 docker-compose 文件!
inputLabel: '您的 docker run 命令:'
inputPlaceholder: 您要转换的 docker run 命令...
downloadBtn: 下载 docker-compose.yml
notComposable: 这些选项无法转换为 docker-compose
notImplemented: 这些选项尚未实现,因此尚未被翻译为 docker-compose
errors: 发生了以下错误

View file

@ -0,0 +1,77 @@
## 配置
设置全局配置
```shell
git config --global user.name "[姓名]"
git config --global user.email "[邮箱]"
```
## 入门
创建一个 git 仓库
```shell
git init
```
克隆现有的 git 仓库
```shell
git clone [url]
```
## 提交
提交所有已跟踪的更改
```shell
git commit -am "[提交信息]"
```
将新修改添加到上次提交中
```shell
git commit --amend --no-edit
```
## 我犯了一个错误
更改上次提交的消息
```shell
git commit --amend
```
撤消最近的提交并保留更改
```shell
git reset HEAD~1
```
撤消最近的 `N` 个提交并保留更改
```shell
git reset HEAD~N
```
撤消最近的提交并放弃更改
```shell
git reset HEAD~1 --hard
```
将分支重置为远程状态
```shell
git fetch origin
git reset --hard origin/[分支名]
```
## 其他
将本地 master 分支重命名为 main
```shell
git branch -m master main
```

View file

@ -1,13 +1,16 @@
<script setup lang="ts">
import { useThemeVars } from 'naive-ui';
import Memo from './git-memo.content.md';
import MemoZH from './git-memo.content.zh.md';
const themeVars = useThemeVars();
const { locale } = useI18n();
</script>
<template>
<div>
<Memo />
<MemoZH v-if="locale === 'zh'" />
<Memo v-else />
</div>
</template>

View file

@ -1,11 +1,11 @@
import { BrandGit } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Git cheatsheet',
name: t('tools.git-memo.title'),
path: '/git-memo',
description:
'Git is a decentralized version management software. With this cheatsheet you will have a quick access to the most common git commands.',
description: t('tools.git-memo.description'),
keywords: ['git', 'push', 'force', 'pull', 'commit', 'amend', 'rebase', 'merge', 'reset', 'soft', 'hard', 'lease'],
component: () => import('./git-memo.vue'),
icon: BrandGit,

View file

@ -0,0 +1,4 @@
tools:
git-memo:
title: Git cheatsheet
description: Git is a decentralized version management software. With this cheatsheet you will have a quick access to the most common git commands.

View file

@ -0,0 +1,4 @@
tools:
git-memo:
title: Git 备忘录
description: Git 是一款分布式版本管理软件。使用这个备忘录,您将快速访问最常用的 Git 命令。

View file

@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'JSON minify',
name: t('tools.json-minify.title'),
path: '/json-minify',
description: 'Minify and compress your JSON by removing unnecessary white spaces.',
description: t('tools.json-minify.description'),
keywords: ['json', 'minify', 'format'],
component: () => import('./json-minify.vue'),
icon: Braces,

View file

@ -3,23 +3,24 @@ import JSON5 from 'json5';
import type { UseValidationRule } from '@/composable/validation';
import { withDefaultOnError } from '@/utils/defaults';
const { t } = useI18n();
const defaultValue = '{\n\t"hello": [\n\t\t"world"\n\t]\n}';
const transformer = (value: string) => withDefaultOnError(() => JSON.stringify(JSON5.parse(value), null, 0), '');
const rules: UseValidationRule<string>[] = [
{
validator: (v: string) => v === '' || JSON5.parse(v),
message: 'Provided JSON is not valid.',
message: t('tools.json-minify.invalidMessage'),
},
];
</script>
<template>
<format-transformer
input-label="Your raw JSON"
:input-label="t('tools.json-minify.inputLabel')"
:input-default="defaultValue"
input-placeholder="Paste your raw JSON here..."
output-label="Minified version of your JSON"
:input-placeholder="t('tools.json-minify.inputPlaceholder')"
:output-label="t('tools.json-minify.outputLabel')"
output-language="json"
:input-validation-rules="rules"
:transformer="transformer"

View file

@ -0,0 +1,10 @@
tools:
json-minify:
title: JSON minify
description: Minify and compress your JSON by removing unnecessary white spaces.
inputLabel: Your raw JSON
inputPlaceholder: Paste your raw JSON here...
outputLabel: Minified version of your JSON
invalidMessage: Provided JSON is not valid.

View file

@ -0,0 +1,10 @@
tools:
json-minify:
title: JSON 压缩
description: 通过删除不必要的空格来压缩和压缩您的 JSON。
inputLabel: 您的原始 JSON
inputPlaceholder: 在此粘贴您的原始 JSON...
outputLabel: 您的 JSON 的压缩版本
invalidMessage: 提供的 JSON 不是有效的。

View file

@ -1,10 +1,11 @@
import { List } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'JSON to CSV',
name: t('tools.json-to-csv.title'),
path: '/json-to-csv',
description: 'Convert JSON to CSV with automatic header detection.',
description: t('tools.json-to-csv.description'),
keywords: ['json', 'to', 'csv', 'convert'],
component: () => import('./json-to-csv.vue'),
icon: List,

View file

@ -13,19 +13,20 @@ function transformer(value: string) {
}, '');
}
const { t } = useI18n();
const rules: UseValidationRule<string>[] = [
{
validator: (v: string) => v === '' || JSON5.parse(v),
message: 'Provided JSON is not valid.',
message: t('tools.json-to-csv.invalidMessage'),
},
];
</script>
<template>
<format-transformer
input-label="Your raw JSON"
input-placeholder="Paste your raw JSON here..."
output-label="CSV version of your JSON"
:input-label="t('tools.json-to-csv.inputLabel')"
:input-placeholder="t('tools.json-to-csv.inputPlaceholder')"
:output-label="t('tools.json-to-csv.outputLabel')"
:input-validation-rules="rules"
:transformer="transformer"
/>

View file

@ -0,0 +1,10 @@
tools:
json-to-csv:
title: JSON to CSV
description: Convert JSON to CSV with automatic header detection.
inputLabel: Your raw JSON
inputPlaceholder: Paste your raw JSON here...
outputLabel: CSV version of your JSON
invalidMessage: Provided JSON is not valid.

View file

@ -0,0 +1,10 @@
tools:
json-to-csv:
title: JSON 转换为 CSV
description: 将 JSON 转换为 CSV并自动检测标题。
inputLabel: 您的原始 JSON
inputPlaceholder: 在此粘贴您的原始 JSON...
outputLabel: 您的 JSON 的 CSV 版本
invalidMessage: 提供的 JSON 不是有效的。

View file

@ -1,10 +1,11 @@
import { Braces } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'JSON prettify and format',
name: t('tools.json-prettify.title'),
path: '/json-prettify',
description: 'Prettify your JSON string to a human friendly readable format.',
description: t('tools.json-prettify.description'),
keywords: ['json', 'viewer', 'prettify', 'format'],
component: () => import('./json-viewer.vue'),
icon: Braces,

View file

@ -6,6 +6,7 @@ import { withDefaultOnError } from '@/utils/defaults';
import { useValidation } from '@/composable/validation';
import TextareaCopyable from '@/components/TextareaCopyable.vue';
const { t } = useI18n();
const inputElement = ref<HTMLElement>();
const rawJson = useStorage('json-prettify:raw-json', '{"hello": "world", "foo": "bar"}');
@ -18,7 +19,7 @@ const rawJsonValidation = useValidation({
rules: [
{
validator: v => v === '' || JSON5.parse(v),
message: 'Provided JSON is not valid.',
message: t('tools.json-prettify.invalidMessage'),
},
],
});
@ -27,24 +28,24 @@ const rawJsonValidation = useValidation({
<template>
<div style="flex: 0 0 100%">
<div style="margin: 0 auto; max-width: 600px" flex justify-center gap-3>
<n-form-item label="Sort keys :" label-placement="left" label-width="100">
<n-form-item :label="t('tools.json-prettify.sortKeys')" label-placement="left" label-width="100">
<n-switch v-model:value="sortKeys" />
</n-form-item>
<n-form-item label="Indent size :" label-placement="left" label-width="100" :show-feedback="false">
<n-form-item :label="t('tools.json-prettify.indentSize')" label-placement="left" label-width="100" :show-feedback="false">
<n-input-number v-model:value="indentSize" min="0" max="10" style="width: 100px" />
</n-form-item>
</div>
</div>
<n-form-item
label="Your raw JSON"
:label="t('tools.json-prettify.rawJSONLabel')"
:feedback="rawJsonValidation.message"
:validation-status="rawJsonValidation.status"
>
<c-input-text
ref="inputElement"
v-model:value="rawJson"
placeholder="Paste your raw JSON here..."
:placeholder="t('tools.json-prettify.rawJSONPlaceholder')"
rows="20"
multiline
autocomplete="off"
@ -54,7 +55,7 @@ const rawJsonValidation = useValidation({
monospace
/>
</n-form-item>
<n-form-item label="Prettified version of your JSON">
<n-form-item :label="t('tools.json-prettify.prettifyJSON')">
<TextareaCopyable :value="cleanJson" language="json" :follow-height-of="inputElement" />
</n-form-item>
</template>

View file

@ -0,0 +1,12 @@
tools:
json-prettify:
title: JSON prettify and format
description: Prettify your JSON string to a human friendly readable format.
sortKeys: 'Sort keys :'
indentSize: 'Indent size :'
rawJSONLabel: Your raw JSON
rawJSONPlaceholder: Paste your raw JSON here...
prettifyJSON: Prettified version of your JSON
invalidMessage: Provided JSON is not valid.

View file

@ -0,0 +1,12 @@
tools:
json-prettify:
title: JSON 格式化工具
description: 将您的 JSON 字符串美化为易读的格式。
sortKeys: '排序键:'
indentSize: '缩进大小:'
rawJSONLabel: 您的原始 JSON
rawJSONPlaceholder: 在此粘贴您的原始 JSON…
prettifyJSON: 您的 JSON 的美化版本
invalidMessage: 提供的 JSON 不是有效的。

View file

@ -1,11 +1,11 @@
import { Qrcode } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'QR Code generator',
name: t('tools.qrcode-generator.title'),
path: '/qrcode-generator',
description:
'Generate and download QR-code for an url or just a text and customize the background and foreground colors.',
description: t('tools.qrcode-generator.description'),
keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent'],
component: () => import('./qr-code-generator.vue'),
icon: Qrcode,

View file

@ -0,0 +1,16 @@
tools:
qrcode-generator:
title: QR Code generator
description: Generate and download QR-code for an url or just a text and customize the background and foreground colors.
low: low
medium: medium
quartile: quartile
high: high
textLabel: 'Text:'
textPlaceholder: 'Your link or text...'
foreground: 'Foreground color:'
background: 'Background color:'
errorCorrectionLevels: 'Error resistance:'
downloadBtn: Download qr-code

View file

@ -0,0 +1,16 @@
tools:
qrcode-generator:
title: 二维码生成器
description: 生成并下载一个网址或文本的二维码,并自定义背景和前景颜色。
low:
medium:
quartile: 四分之一
high:
textLabel: '文本:'
textPlaceholder: '您的链接或文本...'
foreground: '前景色:'
background: '背景色:'
errorCorrectionLevels: '错误纠正等级:'
downloadBtn: 下载二维码

View file

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

View file

@ -1,10 +1,11 @@
import { Server } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Random port generator',
name: t('tools.random-port-generator.title'),
path: '/random-port-generator',
description: 'Generate random port numbers outside of the range of "known" ports (0-1023).',
description: t('tools.random-port-generator.description'),
keywords: ['system', 'port', 'lan', 'generator', 'random', 'development', 'computer'],
component: () => import('./random-port-generator.vue'),
icon: Server,

View file

@ -0,0 +1,8 @@
tools:
random-port-generator:
title: Random port generator
description: Generate random port numbers outside of the range of "known" ports (0-1023).
copyBtn: Copy
refreshBtn: Refresh
copied: Port copied to the clipboard

View file

@ -0,0 +1,8 @@
tools:
random-port-generator:
title: 随机端口生成器
description: 生成在“已知”端口范围之外的随机端口号0-1023
copyBtn: 复制
refreshBtn: 刷新
copied: 端口已复制到剪贴板

View file

@ -3,9 +3,10 @@ import { generatePort } from './random-port-generator.model';
import { computedRefreshable } from '@/composable/computedRefreshable';
import { useCopy } from '@/composable/copy';
const { t } = useI18n();
const [port, refreshPort] = computedRefreshable(() => String(generatePort()));
const { copy } = useCopy({ source: port, text: 'Port copied to the clipboard' });
const { copy } = useCopy({ source: port, text: t('tools.random-port-generator.copied') });
</script>
<template>
@ -15,10 +16,10 @@ const { copy } = useCopy({ source: port, text: 'Port copied to the clipboard' })
</div>
<div flex justify-center gap-3>
<c-button @click="copy()">
Copy
{{ t('tools.random-port-generator.copyBtn') }}
</c-button>
<c-button @click="refreshPort">
Refresh
{{ t('tools.random-port-generator.refreshBtn') }}
</c-button>
</div>
</c-card>

View file

@ -1,10 +1,11 @@
import { Database } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'SQL prettify and format',
name: t('tools.sql-prettify.title'),
path: '/sql-prettify',
description: 'Format and prettify your SQL queries online (it supports various SQL dialects).',
description: t('tools.sql-prettify.description'),
keywords: [
'sql',
'prettify',

View file

@ -0,0 +1,19 @@
tools:
sql-prettify:
title: SQL prettify and format
description: Format and prettify your SQL queries online (it supports various SQL dialects).
dialect: Dialect
keywordCase: Keyword case
indentStyle: Indent style
sql: Standard SQL
upper: UPPERCASE
lower: lowercase
preserve: Preserve
standard: Standard
tabularLeft: Tabular left
tabularRight: Tabular right
inputLabel: Your SQL query
inputPlaceholder: Put your SQL query here...
outputLabel: Prettify version of your query

View file

@ -0,0 +1,19 @@
tools:
sql-prettify:
title: SQL 格式化工具
description: 在线格式化和美化您的 SQL 查询(支持各种 SQL 方言)。
dialect: 方言
keywordCase: 关键词大小写
indentStyle: 缩进样式
sql: 标准 SQL
upper: 大写
lower: 小写
preserve: 保留原样
standard: 标准
tabularLeft: 左对齐
tabularRight: 右对齐
inputLabel: 您的 SQL 查询
inputPlaceholder: 在此处放置您的 SQL 查询...
outputLabel: SQL 查询的美化版本

View file

@ -3,6 +3,7 @@ import { type FormatOptionsWithLanguage, format as formatSQL } from 'sql-formatt
import TextareaCopyable from '@/components/TextareaCopyable.vue';
import { useStyleStore } from '@/stores/style.store';
const { t } = useI18n();
const inputElement = ref<HTMLElement>();
const styleStore = useStyleStore();
const config = reactive<FormatOptionsWithLanguage>({
@ -23,7 +24,7 @@ const prettySQL = computed(() => formatSQL(rawSQL.value, config));
<c-select
v-model:value="config.language"
flex-1
label="Dialect"
:label="t('tools.sql-prettify.dialect')"
:options="[
{ label: 'GCP BigQuery', value: 'bigquery' },
{ label: 'IBM DB2', value: 'db2' },
@ -35,37 +36,37 @@ const prettySQL = computed(() => formatSQL(rawSQL.value, config));
{ label: 'PostgreSQL', value: 'postgresql' },
{ label: 'Amazon Redshift', value: 'redshift' },
{ label: 'Spark', value: 'spark' },
{ label: 'Standard SQL', value: 'sql' },
{ label: t('tools.sql-prettify.sql'), value: 'sql' },
{ label: 'sqlite', value: 'sqlite' },
{ label: 'SQL Server Transact-SQL', value: 'tsql' },
]"
/>
<c-select
v-model:value="config.keywordCase" label="Keyword case"
v-model:value="config.keywordCase" :label="t('tools.sql-prettify.keywordCase')"
flex-1
:options="[
{ label: 'UPPERCASE', value: 'upper' },
{ label: 'lowercase', value: 'lower' },
{ label: 'Preserve', value: 'preserve' },
{ label: t('tools.sql-prettify.upper'), value: 'upper' },
{ label: t('tools.sql-prettify.lower'), value: 'lower' },
{ label: t('tools.sql-prettify.preserve'), value: 'preserve' },
]"
/>
<c-select
v-model:value="config.indentStyle" label="Indent style"
v-model:value="config.indentStyle" :label="t('tools.sql-prettify.indentStyle')"
flex-1
:options="[
{ label: 'Standard', value: 'standard' },
{ label: 'Tabular left', value: 'tabularLeft' },
{ label: 'Tabular right', value: 'tabularRight' },
{ label: t('tools.sql-prettify.standard'), value: 'standard' },
{ label: t('tools.sql-prettify.tabularLeft'), value: 'tabularLeft' },
{ label: t('tools.sql-prettify.tabularRight'), value: 'tabularRight' },
]"
/>
</div>
</div>
<n-form-item label="Your SQL query">
<n-form-item :label="t('tools.sql-prettify.inputLabel')">
<c-input-text
ref="inputElement"
v-model:value="rawSQL"
placeholder="Put your SQL query here..."
:placeholder="t('tools.sql-prettify.inputPlaceholder')"
rows="20"
multiline
autocomplete="off"
@ -75,7 +76,7 @@ const prettySQL = computed(() => formatSQL(rawSQL.value, config));
monospace
/>
</n-form-item>
<n-form-item label="Prettify version of your query">
<n-form-item :label="t('tools.sql-prettify.outputLabel')">
<TextareaCopyable :value="prettySQL" language="sql" :follow-height-of="inputElement" />
</n-form-item>
</template>

View file

@ -1,10 +1,11 @@
import { ImageOutlined } from '@vicons/material';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'SVG placeholder generator',
name: t('tools.svg-placeholder-generator.title'),
path: '/svg-placeholder-generator',
description: 'Generate svg images to use as placeholder in your applications.',
description: t('tools.svg-placeholder-generator.description'),
keywords: ['svg', 'placeholder', 'generator', 'image', 'size', 'mockup'],
component: () => import('./svg-placeholder-generator.vue'),
icon: ImageOutlined,

View file

@ -0,0 +1,21 @@
tools:
svg-placeholder-generator:
title: SVG placeholder generator
description: Generate svg images to use as placeholder in your applications.
widthLabel: 'Width (in px)'
widthPlaceholder: 'SVG width...'
heightLabel: 'Height (in px)'
heightPlaceholder: 'SVG height...'
backgroundLabel: 'Background'
textColorLabel: 'Text color'
fontSizeLabel: 'Font size'
fontSizePlaceholder: 'Font size...'
customTextLabel: 'Custom text'
customTextPlaceholder: 'Default is {width}x{height}'
useExactSize: 'Use exact size'
svgString: 'SVG HTML element'
base64: 'SVG in Base64'
copySvg: 'Copy svg'
copyBase64: 'Copy base64'
downloadSvg: 'Download svg'

View file

@ -0,0 +1,21 @@
tools:
svg-placeholder-generator:
title: SVG 占位符生成器
description: 生成 SVG 图像,用作应用程序中的占位符。
widthLabel: '宽度(以像素为单位)'
widthPlaceholder: 'SVG 宽度...'
heightLabel: '高度(以像素为单位)'
heightPlaceholder: 'SVG 高度...'
backgroundLabel: '背景'
textColorLabel: '文本颜色'
fontSizeLabel: '字体大小'
fontSizePlaceholder: '字体大小...'
customTextLabel: '自定义文本'
customTextPlaceholder: '默认为 {width}x{height}'
useExactSize: '使用精确尺寸'
svgString: 'SVG HTML 元素'
base64: 'Base64 编码的 SVG'
copySvg: '复制 SVG'
copyBase64: '复制 Base64'
downloadSvg: '下载 SVG'

View file

@ -4,6 +4,7 @@ import { useCopy } from '@/composable/copy';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
import { textToBase64 } from '@/utils/base64';
const { t } = useI18n();
const width = ref(600);
const height = ref(350);
const fontSize = ref(26);
@ -35,57 +36,57 @@ const { download } = useDownloadFileFromBase64({ source: base64 });
<div>
<n-form label-placement="left" label-width="100">
<div flex gap-3>
<n-form-item label="Width (in px)" flex-1>
<n-input-number v-model:value="width" placeholder="SVG width..." min="1" />
<n-form-item :label="t('tools.svg-placeholder-generator.widthLabel')" flex-1>
<n-input-number v-model:value="width" :placeholder="t('tools.svg-placeholder-generator.widthPlaceholder')" min="1" />
</n-form-item>
<n-form-item label="Background" flex-1>
<n-form-item :label="t('tools.svg-placeholder-generator.backgroundLabel')" flex-1>
<n-color-picker v-model:value="bgColor" :modes="['hex']" />
</n-form-item>
</div>
<div flex gap-3>
<n-form-item label="Height (in px)" flex-1>
<n-input-number v-model:value="height" placeholder="SVG height..." min="1" />
<n-form-item :label="t('tools.svg-placeholder-generator.heightLabel')" flex-1>
<n-input-number v-model:value="height" :placeholder="t('tools.svg-placeholder-generator.heightPlaceholder')" min="1" />
</n-form-item>
<n-form-item label="Text color" flex-1>
<n-form-item :label="t('tools.svg-placeholder-generator.textColorLabel')" flex-1>
<n-color-picker v-model:value="fgColor" :modes="['hex']" />
</n-form-item>
</div>
<div flex gap-3>
<n-form-item label="Font size" flex-1>
<n-input-number v-model:value="fontSize" placeholder="Font size..." min="1" />
<n-form-item :label="t('tools.svg-placeholder-generator.fontSizeLabel')" flex-1>
<n-input-number v-model:value="fontSize" :placeholder="t('tools.svg-placeholder-generator.fontSizePlaceholder')" min="1" />
</n-form-item>
<c-input-text
v-model:value="customText"
label="Custom text"
:placeholder="`Default is ${width}x${height}`"
:label="t('tools.svg-placeholder-generator.customTextLabel')"
:placeholder="t('tools.svg-placeholder-generator.customTextPlaceholder', { width, height })"
label-position="left"
label-width="100px"
label-align="right"
flex-1
/>
</div>
<n-form-item label="Use exact size" label-placement="left">
<n-form-item :label="t('tools.svg-placeholder-generator.useExactSize')" label-placement="left">
<n-switch v-model:value="useExactSize" />
</n-form-item>
</n-form>
<n-form-item label="SVG HTML element">
<n-form-item :label="t('tools.svg-placeholder-generator.svgString')">
<TextareaCopyable :value="svgString" copy-placement="none" />
</n-form-item>
<n-form-item label="SVG in Base64">
<n-form-item :label="t('tools.svg-placeholder-generator.base64')">
<TextareaCopyable :value="base64" copy-placement="none" />
</n-form-item>
<div flex justify-center gap-3>
<c-button @click="copySVG()">
Copy svg
{{ t('tools.svg-placeholder-generator.copySvg') }}
</c-button>
<c-button @click="copyBase64()">
Copy base64
{{ t('tools.svg-placeholder-generator.copyBase64') }}
</c-button>
<c-button @click="download()">
Download svg
{{ t('tools.svg-placeholder-generator.downloadSvg') }}
</c-button>
</div>
</div>

View file

@ -1,11 +1,11 @@
import { Qrcode } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'WiFi QR Code generator',
name: t('tools.wifi-qrcode-generator.title'),
path: '/wifi-qrcode-generator',
description:
'Generate and download QR-codes for quick connections to WiFi networks.',
description: t('tools.wifi-qrcode-generator.description'),
keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent', 'wifi'],
component: () => import('./wifi-qr-code-generator.vue'),
icon: Qrcode,

View file

@ -0,0 +1,20 @@
tools:
wifi-qrcode-generator:
title: WiFi QR Code generator
description: Generate and download QR-codes for quick connections to WiFi networks.
encryption: Encryption method
ssidLabel: 'SSID:'
ssidPlaceholder: 'Your WiFi SSID...'
hiddenSSID: Hidden SSID
passwordLabel: 'Password:'
passwordPlaceholder: 'Your WiFi Password...'
eapMethod: EAP method
eapIdentityLabel: 'Identity:'
eapIdentityPlaceholder: 'Your EAP Identity...'
anonymous: 'Anonymous?'
eapPhase2Method: 'EAP Phase 2 method'
foreground: 'Foreground color:'
background: 'Background color:'
wifiQrcode: wifi-qrcode
downloadBtn: Download qr-code

View file

@ -0,0 +1,20 @@
tools:
wifi-qrcode-generator:
title: WiFi 二维码生成器
description: 生成并下载用于快速连接 WiFi 网络的二维码。
encryption: 加密方法
ssidLabel: 'SSID:'
ssidPlaceholder: '您的 WiFi SSID...'
hiddenSSID: 隐藏 SSID
passwordLabel: '密码:'
passwordPlaceholder: '您的 WiFi 密码...'
eapMethod: EAP 方法
eapIdentityLabel: '身份:'
eapIdentityPlaceholder: '您的 EAP 身份...'
anonymous: '匿名?'
eapPhase2Method: 'EAP 第2阶段方法'
foreground: '前景色:'
background: '背景色:'
wifiQrcode: WiFi 二维码
downloadBtn: 下载二维码

View file

@ -6,6 +6,7 @@ import {
} from './useQRCode';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
const { t } = useI18n();
const foreground = ref('#000000ff');
const background = ref('#ffffffff');
@ -42,7 +43,7 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
<c-select
v-model:value="encryption"
mb-4
label="Encryption method"
:label="t('tools.wifi-qrcode-generator.encryption')"
default-value="WPA"
label-position="left"
label-width="130px"
@ -72,14 +73,13 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
label-position="left"
label-width="130px"
label-align="right"
label="SSID:"
:label="t('tools.wifi-qrcode-generator.ssidLabel')"
rows="1"
autosize
placeholder="Your WiFi SSID..."
mb-6
:placeholder="t('tools.wifi-qrcode-generator.ssidPlaceholder')"
/>
<n-checkbox v-model:checked="isHiddenSSID">
Hidden SSID
<n-checkbox v-model:checked="isHiddenSSID" w-40>
{{ t('tools.wifi-qrcode-generator.hiddenSSID') }}
</n-checkbox>
</div>
<c-input-text
@ -88,17 +88,17 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
label-position="left"
label-width="130px"
label-align="right"
label="Password:"
:label="t('tools.wifi-qrcode-generator.passwordLabel')"
rows="1"
autosize
type="password"
placeholder="Your WiFi Password..."
:placeholder="t('tools.wifi-qrcode-generator.passwordPlaceholder')"
mb-6
/>
<c-select
v-if="encryption === 'WPA2-EAP'"
v-model:value="eapMethod"
label="EAP method"
:label="t('tools.wifi-qrcode-generator.eapMethod')"
label-position="left"
label-width="130px"
label-align="right"
@ -111,20 +111,19 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
label-position="left"
label-width="130px"
label-align="right"
label="Identity:"
:label="t('tools.wifi-qrcode-generator.eapIdentityLabel')"
rows="1"
autosize
placeholder="Your EAP Identity..."
mb-6
:placeholder="t('tools.wifi-qrcode-generator.eapIdentityPlaceholder')"
/>
<n-checkbox v-model:checked="eapAnonymous">
Anonymous?
<n-checkbox v-model:checked="eapAnonymous" w-40>
{{ t('tools.wifi-qrcode-generator.anonymous') }}
</n-checkbox>
</div>
<c-select
v-if="encryption === 'WPA2-EAP'"
v-model:value="eapPhase2Method"
label="EAP Phase 2 method"
:label="t('tools.wifi-qrcode-generator.eapPhase2Method')"
label-position="left"
label-width="130px"
label-align="right"
@ -132,19 +131,19 @@ const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-c
searchable mb-4
/>
<n-form label-width="130" label-placement="left">
<n-form-item label="Foreground color:">
<n-form-item :label="t('tools.wifi-qrcode-generator.foreground')">
<n-color-picker v-model:value="foreground" :modes="['hex']" />
</n-form-item>
<n-form-item label="Background color:">
<n-form-item :label="t('tools.wifi-qrcode-generator.background')">
<n-color-picker v-model:value="background" :modes="['hex']" />
</n-form-item>
</n-form>
</div>
<div v-if="qrcode">
<div flex flex-col items-center gap-3>
<img alt="wifi-qrcode" :src="qrcode" width="200">
<img :alt="t('tools.wifi-qrcode-generator.wifiQrcode')" :src="qrcode" width="200">
<c-button @click="download">
Download qr-code
{{ t('tools.wifi-qrcode-generator.downloadBtn') }}
</c-button>
</div>
</div>

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: 'XML formatter',
name: t('tools.xml-formatter.title'),
path: '/xml-formatter',
description: 'Prettify your XML string to a human friendly readable format.',
description: t('tools.xml-formatter.description'),
keywords: ['xml', 'prettify', 'format'],
component: () => import('./xml-formatter.vue'),
icon: Code,

View file

@ -0,0 +1,12 @@
tools:
xml-formatter:
title: XML formatter
description: Prettify your XML string to a human friendly readable format.
collapseContent: 'Collapse content:'
indentSize: 'Indent size:'
inputLabel: Your XML
inputPlaceholder: Paste your XML here...
outputLabel: Formatted XML from your XML
invalidMessage: Provided XML is not valid.

View file

@ -0,0 +1,12 @@
tools:
xml-formatter:
title: XML 格式化工具
description: 将您的 XML 字符串美化为易读的人类友好格式。
collapseContent: '折叠内容:'
indentSize: '缩进大小:'
inputLabel: 您的 XML
inputPlaceholder: 在此粘贴您的 XML...
outputLabel: 根据您的 XML 格式化的 XML
invalidMessage: 提供的 XML 不合法。

View file

@ -2,6 +2,7 @@
import { formatXml, isValidXML } from './xml-formatter.service';
import type { UseValidationRule } from '@/composable/validation';
const { t } = useI18n();
const defaultValue = '<hello><world>foo</world><world>bar</world></hello>';
const indentSize = useStorage('xml-formatter:indent-size', 2);
const collapseContent = useStorage('xml-formatter:collapse-content', true);
@ -17,7 +18,7 @@ function transformer(value: string) {
const rules: UseValidationRule<string>[] = [
{
validator: isValidXML,
message: 'Provided XML is not valid.',
message: t('tools.xml-formatter.invalidMessage'),
},
];
</script>
@ -25,19 +26,19 @@ const rules: UseValidationRule<string>[] = [
<template>
<div important:flex-full important:flex-shrink-0 important:flex-grow-0>
<div flex justify-center>
<n-form-item label="Collapse content:" label-placement="left">
<n-form-item :label="t('tools.xml-formatter.collapseContent')" label-placement="left">
<n-switch v-model:value="collapseContent" />
</n-form-item>
<n-form-item label="Indent size:" label-placement="left" label-width="100" :show-feedback="false">
<n-form-item :label="t('tools.xml-formatter.indentSize')" label-placement="left" label-width="100" :show-feedback="false">
<n-input-number v-model:value="indentSize" min="0" max="10" w-100px />
</n-form-item>
</div>
</div>
<format-transformer
input-label="Your XML"
input-placeholder="Paste your XML here..."
output-label="Formatted XML from your XML"
:input-label="t('tools.xml-formatter.inputLabel')"
:input-placeholder="t('tools.xml-formatter.inputPlaceholder')"
:output-label="t('tools.xml-formatter.outputLabel')"
output-language="xml"
:input-validation-rules="rules"
:transformer="transformer"