WIP(translate): translate web and math category all tools

This commit is contained in:
Amery2010 2024-02-21 01:37:59 +08:00
parent 4f550a9499
commit a2e498d0aa
37 changed files with 406 additions and 119 deletions

View file

@ -1,6 +1,7 @@
import { formatDuration } from 'date-fns';
import type { Locale } from 'date-fns';
export function formatMsDuration(duration: number) {
export function formatMsDuration(duration: number, locale: Locale) {
const ms = Math.floor(duration % 1000);
const secs = Math.floor(((duration - ms) / 1000) % 60);
const mins = Math.floor((((duration - ms) / 1000 - secs) / 60) % 60);
@ -11,6 +12,6 @@ export function formatMsDuration(duration: number) {
hours: hrs,
minutes: mins,
seconds: secs,
}) + (ms > 0 ? ` ${ms} ms` : '')
}, { locale }) + (ms > 0 ? ` ${ms} ms` : '')
);
}

View file

@ -3,57 +3,58 @@
import { addMilliseconds, formatRelative } from 'date-fns';
import { enGB } from 'date-fns/locale';
import { enGB, zhCN } from 'date-fns/locale';
import { formatMsDuration } from './eta-calculator.service';
const { t, locale } = useI18n();
const unitCount = ref(3 * 62);
const unitPerTimeSpan = ref(3);
const timeSpan = ref(5);
const timeSpanUnitMultiplier = ref(60000);
const startedAt = ref(Date.now());
const localeLang = computed(() => locale.value === 'zh' ? zhCN : enGB);
const durationMs = computed(() => {
const timeSpanMs = timeSpan.value * timeSpanUnitMultiplier.value;
return unitCount.value / (unitPerTimeSpan.value / timeSpanMs);
});
const endAt = computed(() =>
formatRelative(addMilliseconds(startedAt.value, durationMs.value), Date.now(), { locale: enGB }),
formatRelative(addMilliseconds(startedAt.value, durationMs.value), Date.now(), { locale: localeLang.value }),
);
</script>
<template>
<div>
<div text-justify op-70>
With a concrete example, if you wash 5 plates in 3 minutes and you have 500 plates to wash, it will take you 5
hours to wash them all.
{{ t('tools.eta-calculator.tips') }}
</div>
<n-divider />
<div flex gap-2>
<n-form-item label="Amount of element to consume" flex-1>
<n-form-item :label="t('tools.eta-calculator.unitCount')" flex-1>
<n-input-number v-model:value="unitCount" :min="1" />
</n-form-item>
<n-form-item label="The consumption started at" flex-1>
<n-form-item :label="t('tools.eta-calculator.startedAt')" flex-1>
<n-date-picker v-model:value="startedAt" type="datetime" />
</n-form-item>
</div>
<p>Amount of unit consumed by time span</p>
<p>{{ t('tools.eta-calculator.unitPerTimeSpan') }}</p>
<div flex flex-col items-baseline gap-y-2 md:flex-row>
<n-input-number v-model:value="unitPerTimeSpan" :min="1" />
<div flex items-baseline gap-2>
<span ml-2>in</span>
<span ml-2>{{ t('tools.eta-calculator.in') }}</span>
<n-input-number v-model:value="timeSpan" min-w-130px :min="1" />
<c-select
v-model:value="timeSpanUnitMultiplier"
min-w-130px
:options="[
{ label: 'milliseconds', value: 1 },
{ label: 'seconds', value: 1000 },
{ label: 'minutes', value: 1000 * 60 },
{ label: 'hours', value: 1000 * 60 * 60 },
{ label: 'days', value: 1000 * 60 * 60 * 24 },
{ label: t('tools.eta-calculator.milliseconds'), value: 1 },
{ label: t('tools.eta-calculator.seconds'), value: 1000 },
{ label: t('tools.eta-calculator.minutes'), value: 1000 * 60 },
{ label: t('tools.eta-calculator.hours'), value: 1000 * 60 * 60 },
{ label: t('tools.eta-calculator.days'), value: 1000 * 60 * 60 * 24 },
]"
/>
</div>
@ -61,12 +62,12 @@ const endAt = computed(() =>
<n-divider />
<c-card mb-2>
<n-statistic label="Total duration">
{{ formatMsDuration(durationMs) }}
<n-statistic :label="t('tools.eta-calculator.totalDuration')">
{{ formatMsDuration(durationMs, localeLang) }}
</n-statistic>
</c-card>
<c-card>
<n-statistic label="It will end ">
<n-statistic :label="t('tools.eta-calculator.endAt')">
{{ endAt }}
</n-statistic>
</c-card>

View file

@ -1,11 +1,11 @@
import { Hourglass } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'ETA calculator',
name: t('tools.eta-calculator.title'),
path: '/eta-calculator',
description:
'An ETA (Estimated Time of Arrival) calculator to know the approximate end time of a task, for example the moment of ending of a download.',
description: t('tools.eta-calculator.description'),
keywords: ['eta', 'calculator', 'estimated', 'time', 'arrival', 'average'],
component: () => import('./eta-calculator.vue'),
icon: Hourglass,

View file

@ -0,0 +1,17 @@
tools:
eta-calculator:
title: ETA calculator
description: An ETA (Estimated Time of Arrival) calculator to know the approximate end time of a task, for example the moment of ending of a download.
tips: With a concrete example, if you wash 5 plates in 3 minutes and you have 500 plates to wash, it will take you 5 hours to wash them all.
unitCount: Amount of element to consume
startedAt: The consumption started at
unitPerTimeSpan: Amount of unit consumed by time span
in: in
milliseconds: milliseconds
seconds: seconds
minutes: minutes
hours: hours
days: days
totalDuration: Total duration
endAt: It will end

View file

@ -0,0 +1,17 @@
tools:
eta-calculator:
title: 预计到达时间计算器
description: 一个预计到达时间ETA计算器用于了解任务的大致结束时间例如下载结束的时间。
tips: 以一个具体的例子来说,如果你用 3 分钟洗 5 个盘子,而你有 500 个盘子要洗,那么你需要 5 个小时来洗完所有盘子。
unitCount: 要消耗的元素数量
startedAt: 消耗开始于
unitPerTimeSpan: 每个时间段消耗的单位数量
in:
milliseconds: 毫秒
seconds:
minutes: 分钟
hours: 小时
days:
totalDuration: 总持续时间
endAt: 它将在以下时间结束

View file

@ -1,10 +1,11 @@
import { Binary } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Ipv4 address converter',
name: t('tools.ipv4-address-converter.title'),
path: '/ipv4-address-converter',
description: 'Convert an ip address into decimal, binary, hexadecimal or event in ipv6',
description: t('tools.ipv4-address-converter.description'),
keywords: ['ipv4', 'address', 'converter', 'decimal', 'hexadecimal', 'binary', 'ipv6'],
component: () => import('./ipv4-address-converter.vue'),
icon: Binary,

View file

@ -3,6 +3,7 @@ import { convertBase } from '../integer-base-converter/integer-base-converter.mo
import { ipv4ToInt, ipv4ToIpv6, isValidIpv4 } from './ipv4-address-converter.service';
import { useValidation } from '@/composable/validation';
const { t } = useI18n();
const rawIpAddress = useStorage('ipv4-converter:ip', '192.168.1.1');
const convertedSections = computed(() => {
@ -10,23 +11,23 @@ const convertedSections = computed(() => {
return [
{
label: 'Decimal: ',
label: t('tools.ipv4-address-converter.decimal'),
value: String(ipInDecimal),
},
{
label: 'Hexadecimal: ',
label: t('tools.ipv4-address-converter.hexadecimal'),
value: convertBase({ fromBase: 10, toBase: 16, value: String(ipInDecimal) }).toUpperCase(),
},
{
label: 'Binary: ',
label: t('tools.ipv4-address-converter.binary'),
value: convertBase({ fromBase: 10, toBase: 2, value: String(ipInDecimal) }),
},
{
label: 'Ipv6: ',
label: t('tools.ipv4-address-converter.ipv6'),
value: ipv4ToIpv6({ ip: rawIpAddress.value }),
},
{
label: 'Ipv6 (short): ',
label: t('tools.ipv4-address-converter.ipv6Short'),
value: ipv4ToIpv6({ ip: rawIpAddress.value, prefix: '::ffff:' }),
},
];
@ -34,13 +35,13 @@ const convertedSections = computed(() => {
const { attrs: validationAttrs } = useValidation({
source: rawIpAddress,
rules: [{ message: 'Invalid ipv4 address', validator: ip => isValidIpv4({ ip }) }],
rules: [{ message: t('tools.ipv4-address-converter.invalidMessage'), validator: ip => isValidIpv4({ ip }) }],
});
</script>
<template>
<div>
<c-input-text v-model:value="rawIpAddress" label="The ipv4 address:" placeholder="The ipv4 address..." />
<c-input-text v-model:value="rawIpAddress" :label="t('tools.ipv4-address-converter.ipv4AddressLabel')" :placeholder="t('tools.ipv4-address-converter.ipv4AddressPlaceholder')" />
<n-divider />
@ -49,11 +50,11 @@ const { attrs: validationAttrs } = useValidation({
:key="label"
:label="label"
label-position="left"
label-width="100px"
label-width="120px"
label-align="right"
mb-2
:value="validationAttrs.validationStatus === 'error' ? '' : value"
placeholder="Set a correct ipv4 address"
:placeholder="t('tools.ipv4-address-converter.errorPlaceholder')"
/>
</div>
</template>

View file

@ -0,0 +1,15 @@
tools:
ipv4-address-converter:
title: Ipv4 address converter
description: Convert an ip address into decimal, binary, hexadecimal or event in ipv6
ipv4AddressLabel: 'The ipv4 address:'
ipv4AddressPlaceholder: 'The ipv4 address...'
decimal: 'Decimal: '
hexadecimal: 'Hexadecimal: '
binary: 'Binary: '
ipv6: 'Ipv6: '
ipv6Short: 'Ipv6 (short): '
errorPlaceholder: Set a correct ipv4 address
invalidMessage: Invalid ipv4 address

View file

@ -0,0 +1,15 @@
tools:
ipv4-address-converter:
title: IPv4 地址转换器
description: 将 IP 地址转换为十进制、二进制、十六进制,甚至 IPv6
ipv4AddressLabel: 'IPv4 地址:'
ipv4AddressPlaceholder: 'IPv4 地址...'
decimal: '十进制:'
hexadecimal: '十六进制:'
binary: '二进制:'
ipv6: 'IPv6'
ipv6Short: 'IPv6简写'
errorPlaceholder: 设置一个正确的 IPv4 地址
invalidMessage: 无效的 IPv4 地址

View file

@ -1,11 +1,11 @@
import { UnfoldMoreOutlined } from '@vicons/material';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'IPv4 range expander',
name: t('tools.ipv4-range-expander.title'),
path: '/ipv4-range-expander',
description:
'Given a start and an end IPv4 address this tool calculates a valid IPv4 network with its CIDR notation.',
description: t('tools.ipv4-range-expander.description'),
keywords: ['ipv4', 'range', 'expander', 'subnet', 'creator', 'cidr'],
component: () => import('./ipv4-range-expander.vue'),
icon: UnfoldMoreOutlined,

View file

@ -6,6 +6,7 @@ import { calculateCidr } from './ipv4-range-expander.service';
import ResultRow from './result-row.vue';
import { useValidation } from '@/composable/validation';
const { t } = useI18n();
const rawStartAddress = useStorage('ipv4-range-expander:startAddress', '192.168.1.1');
const rawEndAddress = useStorage('ipv4-range-expander:endAddress', '192.168.6.255');
@ -17,22 +18,22 @@ const calculatedValues: {
getNewValue: (result: Ipv4RangeExpanderResult | undefined) => string | undefined
}[] = [
{
label: 'Start address',
label: t('tools.ipv4-range-expander.startAddress'),
getOldValue: () => rawStartAddress.value,
getNewValue: result => result?.newStart,
},
{
label: 'End address',
label: t('tools.ipv4-range-expander.endAddress'),
getOldValue: () => rawEndAddress.value,
getNewValue: result => result?.newEnd,
},
{
label: 'Addresses in range',
label: t('tools.ipv4-range-expander.addressesInRange'),
getOldValue: result => result?.oldSize?.toLocaleString(),
getNewValue: result => result?.newSize?.toLocaleString(),
},
{
label: 'CIDR',
label: t('tools.ipv4-range-expander.CIDR'),
getOldValue: () => '',
getNewValue: result => result?.newCidr,
},
@ -40,11 +41,11 @@ const calculatedValues: {
const startIpValidation = useValidation({
source: rawStartAddress,
rules: [{ message: 'Invalid ipv4 address', validator: ip => isValidIpv4({ ip }) }],
rules: [{ message: t('tools.ipv4-range-expander.invalidMessage'), validator: ip => isValidIpv4({ ip }) }],
});
const endIpValidation = useValidation({
source: rawEndAddress,
rules: [{ message: 'Invalid ipv4 address', validator: ip => isValidIpv4({ ip }) }],
rules: [{ message: t('tools.ipv4-range-expander.invalidMessage'), validator: ip => isValidIpv4({ ip }) }],
});
const showResult = computed(() => endIpValidation.isValid && startIpValidation.isValid && result.value !== undefined);
@ -61,16 +62,16 @@ function onSwitchStartEndClicked() {
<div mb-4 flex gap-4>
<c-input-text
v-model:value="rawStartAddress"
label="Start address"
placeholder="Start IPv4 address..."
:label="t('tools.ipv4-range-expander.startAddress')"
:placeholder="t('tools.ipv4-range-expander.startAddressPlaceholder')"
:validation="startIpValidation"
clearable
/>
<c-input-text
v-model:value="rawEndAddress"
label="End address"
placeholder="End IPv4 address..."
:label="t('tools.ipv4-range-expander.endAddress')"
:placeholder="t('tools.ipv4-range-expander.endAddressPlaceholder')"
:validation="endIpValidation"
clearable
/>
@ -83,10 +84,10 @@ function onSwitchStartEndClicked() {
&nbsp;
</th>
<th scope="col">
old value
{{ t('tools.ipv4-range-expander.oldValue') }}
</th>
<th scope="col">
new value
{{ t('tools.ipv4-range-expander.newValue') }}
</th>
</tr>
</thead>
@ -102,17 +103,16 @@ function onSwitchStartEndClicked() {
</n-table>
<n-alert
v-else-if="startIpValidation.isValid && endIpValidation.isValid"
title="Invalid combination of start and end IPv4 address"
:title="t('tools.ipv4-range-expander.errorMessage')"
type="error"
>
<div my-3 op-70>
The end IPv4 address is lower than the start IPv4 address. This is not valid and no result could be calculated.
In the most cases the solution to solve this problem is to change start and end address.
{{ t('tools.ipv4-range-expander.errorDesc') }}
</div>
<c-button @click="onSwitchStartEndClicked">
<n-icon mr-2 :component="Exchange" depth="3" size="22" />
Switch start and end IPv4 address
{{ t('tools.ipv4-range-expander.switchStartEnd') }}
</c-button>
</n-alert>
</div>

View file

@ -0,0 +1,18 @@
tools:
ipv4-range-expander:
title: IPv4 range expander
description: Given a start and an end IPv4 address this tool calculates a valid IPv4 network with its CIDR notation.
startAddressPlaceholder: Start IPv4 address...
endAddressPlaceholder: End IPv4 address...
startAddress: Start address
endAddress: End address
addressesInRange: Addresses in range
CIDR: CIDR
oldValue: old value
newValue: new value
errorMessage: Invalid combination of start and end IPv4 address
errorDesc: The end IPv4 address is lower than the start IPv4 address. This is not valid and no result could be calculated. In the most cases the solution to solve this problem is to change start and end address.
switchStartEnd: Switch start and end IPv4 address
invalidMessage: Invalid ipv4 address

View file

@ -0,0 +1,18 @@
tools:
ipv4-range-expander:
title: IPv4 范围扩展器
description: 给定起始和结束 IPv4 地址,此工具计算出一个有效的 IPv4 网络及其 CIDR 表示法。
startAddressPlaceholder: 起始 IPv4 地址...
endAddressPlaceholder: 结束 IPv4 地址...
startAddress: 起始地址
endAddress: 结束地址
addressesInRange: 范围内的地址
CIDR: CIDR
oldValue: 旧值
newValue: 新值
errorMessage: 起始和结束 IPv4 地址的组合无效
errorDesc: 结束 IPv4 地址低于起始 IPv4 地址。这是无效的,无法计算结果。大多数情况下,解决此问题的方法是更改起始和结束地址。
switchStartEnd: 切换起始和结束 IPv4 地址
invalidMessage: 无效的 IPv4 地址

View file

@ -1,10 +1,11 @@
import { RouterOutlined } from '@vicons/material';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'IPv4 subnet calculator',
name: t('tools.ipv4-subnet-calculator.title'),
path: '/ipv4-subnet-calculator',
description: 'Parse your IPv4 CIDR blocks and get all the info you need about your sub network.',
description: t('tools.ipv4-subnet-calculator.description'),
keywords: ['ipv4', 'subnet', 'calculator', 'mask', 'network', 'cidr', 'netmask', 'bitmask', 'broadcast', 'address'],
component: () => import('./ipv4-subnet-calculator.vue'),
icon: RouterOutlined,

View file

@ -7,6 +7,7 @@ import { withDefaultOnError } from '@/utils/defaults';
import { isNotThrowing } from '@/utils/boolean';
import SpanCopyable from '@/components/SpanCopyable.vue';
const { t } = useI18n();
const ip = useStorage('ipv4-subnet-calculator:ip', '192.168.0.1/24');
const getNetworkInfo = (address: string) => new Netmask(address.trim());
@ -15,7 +16,7 @@ const networkInfo = computed(() => withDefaultOnError(() => getNetworkInfo(ip.va
const ipValidationRules = [
{
message: 'We cannot parse this address, check the format',
message: t('tools.ipv4-subnet-calculator.invalidMessage'),
validator: (value: string) => isNotThrowing(() => getNetworkInfo(value.trim())),
},
];
@ -26,50 +27,50 @@ const sections: {
undefinedFallback?: string
}[] = [
{
label: 'Netmask',
label: t('tools.ipv4-subnet-calculator.networkMask'),
getValue: block => block.toString(),
},
{
label: 'Network address',
label: t('tools.ipv4-subnet-calculator.networkAddress'),
getValue: ({ base }) => base,
},
{
label: 'Network mask',
label: t('tools.ipv4-subnet-calculator.networkMask'),
getValue: ({ mask }) => mask,
},
{
label: 'Network mask in binary',
label: t('tools.ipv4-subnet-calculator.networkMaskInBinary'),
getValue: ({ bitmask }) => ('1'.repeat(bitmask) + '0'.repeat(32 - bitmask)).match(/.{8}/g)?.join('.') ?? '',
},
{
label: 'CIDR notation',
label: t('tools.ipv4-subnet-calculator.CIDRNotation'),
getValue: ({ bitmask }) => `/${bitmask}`,
},
{
label: 'Wildcard mask',
label: t('tools.ipv4-subnet-calculator.wildcardMask'),
getValue: ({ hostmask }) => hostmask,
},
{
label: 'Network size',
label: t('tools.ipv4-subnet-calculator.networkSize'),
getValue: ({ size }) => String(size),
},
{
label: 'First address',
label: t('tools.ipv4-subnet-calculator.firstAddress'),
getValue: ({ first }) => first,
},
{
label: 'Last address',
label: t('tools.ipv4-subnet-calculator.lastAddress'),
getValue: ({ last }) => last,
},
{
label: 'Broadcast address',
label: t('tools.ipv4-subnet-calculator.broadcastAddress'),
getValue: ({ broadcast }) => broadcast,
undefinedFallback: 'No broadcast address with this mask',
undefinedFallback: t('tools.ipv4-subnet-calculator.broadcastFallback'),
},
{
label: 'IP class',
label: t('tools.ipv4-subnet-calculator.IPClass'),
getValue: ({ base: ip }) => getIPClass({ ip }),
undefinedFallback: 'Unknown class type',
undefinedFallback: t('tools.ipv4-subnet-calculator.IPClassFallback'),
},
];
@ -86,8 +87,8 @@ function switchToBlock({ count = 1 }: { count?: number }) {
<div>
<c-input-text
v-model:value="ip"
label="An IPv4 address with or without mask"
placeholder="The ipv4 address..."
:label="t('tools.ipv4-subnet-calculator.ipv4AddressLabel')"
:placeholder="t('tools.ipv4-subnet-calculator.ipv4AddressPlaceholder')"
:validation-rules="ipValidationRules"
mb-4
/>
@ -112,10 +113,10 @@ function switchToBlock({ count = 1 }: { count?: number }) {
<div mt-3 flex items-center justify-between>
<c-button @click="switchToBlock({ count: -1 })">
<n-icon :component="ArrowLeft" />
Previous block
{{ t('tools.ipv4-subnet-calculator.previousBlock') }}
</c-button>
<c-button @click="switchToBlock({ count: 1 })">
Next block
{{ t('tools.ipv4-subnet-calculator.nextBlock') }}
<n-icon :component="ArrowRight" />
</c-button>
</div>

View file

@ -0,0 +1,24 @@
tools:
ipv4-subnet-calculator:
title: IPv4 subnet calculator
description: Parse your IPv4 CIDR blocks and get all the info you need about your sub network.
ipv4AddressLabel: An IPv4 address with or without mask
ipv4AddressPlaceholder: The ipv4 address...
netmask: Netmask
networkAddress: Network address
networkMask: Network mask
networkMaskInBinary: Network mask in binary
CIDRNotation: CIDR notation
wildcardMask: Wildcard mask
networkSize: Network size
firstAddress: First address
lastAddress: Last address
broadcastAddress: Broadcast address
broadcastFallback: No broadcast address with this mask
IPClass: IP class
IPClassFallback: Unknown class type
previousBlock: Previous block
nextBlock: Next block
invalidMessage: We cannot parse this address, check the format

View file

@ -0,0 +1,24 @@
tools:
ipv4-subnet-calculator:
title: IPv4 子网计算器
description: 解析您的 IPv4 CIDR 块,并获取关于您的子网络的所有所需信息。
ipv4AddressLabel: 带有或不带掩码的 IPv4 地址
ipv4AddressPlaceholder: IPv4 地址...
netmask: 子网掩码
networkAddress: 网络地址
networkMask: 网络掩码
networkMaskInBinary: 二进制网络掩码
CIDRNotation: CIDR 表示法
wildcardMask: 通配符掩码
networkSize: 网络大小
firstAddress: 第一个地址
lastAddress: 最后一个地址
broadcastAddress: 广播地址
broadcastFallback: 此掩码无广播地址
IPClass: IP 类别
IPClassFallback: 未知类别
previousBlock: 前一个块
nextBlock: 下一个块
invalidMessage: 我们无法解析此地址,请检查格式

View file

@ -1,10 +1,11 @@
import { BuildingFactory } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'IPv6 ULA generator',
name: t('tools.ipv6-ula-generator.title'),
path: '/ipv6-ula-generator',
description: 'Generate your own local, non-routable IP addresses on your network according to RFC4193.',
description: t('tools.ipv6-ula-generator.description'),
keywords: ['ipv6', 'ula', 'generator', 'rfc4193', 'network', 'private'],
component: () => import('./ipv6-ula-generator.vue'),
icon: BuildingFactory,

View file

@ -3,6 +3,7 @@ import { SHA1 } from 'crypto-js';
import InputCopyable from '@/components/InputCopyable.vue';
import { macAddressValidation } from '@/utils/macAddress';
const { t } = useI18n();
const macAddress = ref('20:37:06:12:34:56');
const calculatedSections = computed(() => {
const timestamp = new Date().getTime();
@ -14,15 +15,15 @@ const calculatedSections = computed(() => {
return [
{
label: 'IPv6 ULA:',
label: t('tools.ipv6-ula-generator.IPv6ULA'),
value: `${ula}::/48`,
},
{
label: 'First routable block:',
label: t('tools.ipv6-ula-generator.firstRoutableBlock'),
value: `${ula}:0::/64`,
},
{
label: 'Last routable block:',
label: t('tools.ipv6-ula-generator.lastRoutableBlock'),
value: `${ula}:ffff::/64`,
},
];
@ -33,16 +34,15 @@ const addressValidation = macAddressValidation(macAddress);
<template>
<div>
<n-alert title="Info" type="info">
This tool uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed,
and the lower 40 bits to generate your random ULA.
<n-alert :title="t('tools.ipv6-ula-generator.info')" type="info">
{{ t('tools.ipv6-ula-generator.infoDetail') }}
</n-alert>
<c-input-text
v-model:value="macAddress"
placeholder="Type a MAC address"
:placeholder="t('tools.ipv6-ula-generator.macAddressPlaceholder')"
clearable
label="MAC address:"
:label="t('tools.ipv6-ula-generator.macAddressLabel')"
raw-text
my-8
:validation="addressValidation"

View file

@ -0,0 +1,13 @@
tools:
ipv6-ula-generator:
title: IPv6 ULA generator
description: Generate your own local, non-routable IP addresses on your network according to RFC4193.
macAddressLabel: 'MAC address:'
macAddressPlaceholder: 'Type a MAC address'
IPv6ULA: 'IPv6 ULA:'
firstRoutableBlock: 'First routable block:'
lastRoutableBlock: 'Last routable block:'
info: Info
infoDetail: This tool uses the first method suggested by IETF using the current timestamp plus the mac address, sha1 hashed, and the lower 40 bits to generate your random ULA.

View file

@ -0,0 +1,13 @@
tools:
ipv6-ula-generator:
title: IPv6 ULA 生成器
description: 根据 RFC4193在您的网络中生成您自己的本地、不可路由的 IP 地址。
macAddressLabel: 'MAC 地址:'
macAddressPlaceholder: '输入 MAC 地址'
IPv6ULA: 'IPv6 ULA:'
firstRoutableBlock: '第一个可路由的块:'
lastRoutableBlock: '最后一个可路由的块:'
info: 信息
infoDetail: 此工具使用 IETF 建议的第一种方法,使用当前时间戳加上 MAC 地址,进行 sha1 哈希处理,并使用低 40 位生成您的随机 ULA。

View file

@ -1,10 +1,11 @@
import { Devices } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'MAC address generator',
name: t('tools.mac-address-generator.title'),
path: '/mac-address-generator',
description: 'Enter the quantity and prefix. MAC addresses will be generated in your chosen case (uppercase or lowercase)',
description: t('tools.mac-address-generator.description'),
keywords: ['mac', 'address', 'generator', 'random', 'prefix'],
component: () => import('./mac-address-generator.vue'),
icon: Devices,

View file

@ -0,0 +1,17 @@
tools:
mac-address-generator:
title: MAC address generator
description: Enter the quantity and prefix. MAC addresses will be generated in your chosen case (uppercase or lowercase)
quantity: 'Quantity:'
prefixLabel: 'MAC address prefix:'
prefixPlaceholder: 'Set a prefix, e.g. 64:16:7F'
case: 'Case:'
separator: 'Separator:'
uppercase: Uppercase
lowercase: Lowercase
none: None
refreshBtn: Refresh
copyBtn: Copy
copied: MAC addresses copied to the clipboard

View file

@ -0,0 +1,17 @@
tools:
mac-address-generator:
title: MAC 地址生成器
description: 输入数量和前缀。MAC 地址将以您选择的大小写形式(大写或小写)生成。
quantity: '数量:'
prefixLabel: 'MAC 地址前缀:'
prefixPlaceholder: '设置前缀,例如 64:16:7F'
case: '大小写:'
separator: '分隔符:'
uppercase: 大写
lowercase: 小写
none:
refreshBtn: 刷新
copyBtn: 复制
copied: MAC 地址已复制到剪贴板

View file

@ -5,14 +5,15 @@ import { computedRefreshable } from '@/composable/computedRefreshable';
import { useCopy } from '@/composable/copy';
import { usePartialMacAddressValidation } from '@/utils/macAddress';
const { t } = useI18n();
const amount = useStorage('mac-address-generator-amount', 1);
const macAddressPrefix = useStorage('mac-address-generator-prefix', '64:16:7F');
const prefixValidation = usePartialMacAddressValidation(macAddressPrefix);
const casesTransformers = [
{ label: 'Uppercase', value: (value: string) => value.toUpperCase() },
{ label: 'Lowercase', value: (value: string) => value.toLowerCase() },
{ label: t('tools.mac-address-generator.uppercase'), value: (value: string) => value.toUpperCase() },
{ label: t('tools.mac-address-generator.lowercase'), value: (value: string) => value.toLowerCase() },
];
const caseTransformer = ref(casesTransformers[0].value);
@ -30,7 +31,7 @@ const separators = [
value: '.',
},
{
label: 'None',
label: t('tools.mac-address-generator.none'),
value: '',
},
];
@ -48,20 +49,20 @@ const [macAddresses, refreshMacAddresses] = computedRefreshable(() => {
return ids.join('\n');
});
const { copy } = useCopy({ source: macAddresses, text: 'MAC addresses copied to the clipboard' });
const { copy } = useCopy({ source: macAddresses, text: t('tools.mac-address-generator.copied') });
</script>
<template>
<div flex flex-col justify-center gap-2>
<div flex items-center>
<label w-150px pr-12px text-right> Quantity:</label>
<label w-150px pr-12px text-right> {{ t('tools.mac-address-generator.quantity') }}</label>
<n-input-number v-model:value="amount" min="1" max="100" flex-1 />
</div>
<c-input-text
v-model:value="macAddressPrefix"
label="MAC address prefix:"
placeholder="Set a prefix, e.g. 64:16:7F"
:label="t('tools.mac-address-generator.prefixLabel')"
:placeholder="t('tools.mac-address-generator.prefixPlaceholder')"
clearable
label-position="left"
spellcheck="false"
@ -74,7 +75,7 @@ const { copy } = useCopy({ source: macAddresses, text: 'MAC addresses copied to
<c-buttons-select
v-model:value="caseTransformer"
:options="casesTransformers"
label="Case:"
:label="t('tools.mac-address-generator.case')"
label-width="150px"
label-align="right"
/>
@ -82,7 +83,7 @@ const { copy } = useCopy({ source: macAddresses, text: 'MAC addresses copied to
<c-buttons-select
v-model:value="separator"
:options="separators"
label="Separator:"
:label="t('tools.mac-address-generator.separator')"
label-width="150px"
label-align="right"
/>
@ -93,10 +94,10 @@ const { copy } = useCopy({ source: macAddresses, text: 'MAC addresses copied to
<div flex justify-center gap-2>
<c-button data-test-id="refresh" @click="refreshMacAddresses()">
Refresh
{{ t('tools.mac-address-generator.refreshBtn') }}
</c-button>
<c-button @click="copy()">
Copy
{{ t('tools.mac-address-generator.copyBtn') }}
</c-button>
</div>
</div>

View file

@ -1,10 +1,11 @@
import { Devices } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'MAC address lookup',
name: t('tools.mac-address-lookup.title'),
path: '/mac-address-lookup',
description: 'Find the vendor and manufacturer of a device by its MAC address.',
description: t('tools.mac-address-lookup.description'),
keywords: ['mac', 'address', 'lookup', 'vendor', 'parser', 'manufacturer'],
component: () => import('./mac-address-lookup.vue'),
icon: Devices,

View file

@ -0,0 +1,12 @@
tools:
mac-address-lookup:
title: MAC address lookup
description: Find the vendor and manufacturer of a device by its MAC address.
MACAddressLabel: 'MAC address:'
MACAddressPlaceholder: Type a MAC address
vendorInfo: 'Vendor info:'
unknownAddress: Unknown vendor for this address
copyBtn: Copy vendor info
copied: Vendor info copied to the clipboard

View file

@ -0,0 +1,12 @@
tools:
mac-address-lookup:
title: MAC 地址查询
description: 通过 MAC 地址查找设备的供应商和制造商。
MACAddressLabel: 'MAC 地址:'
MACAddressPlaceholder: 输入 MAC 地址
vendorInfo: '供应商信息:'
unknownAddress: 此地址的供应商未知
copyBtn: 复制供应商信息
copied: 供应商信息已复制到剪贴板

View file

@ -3,21 +3,22 @@ import db from 'oui-data';
import { macAddressValidationRules } from '@/utils/macAddress';
import { useCopy } from '@/composable/copy';
const { t } = useI18n();
const getVendorValue = (address: string) => address.trim().replace(/[.:-]/g, '').toUpperCase().substring(0, 6);
const macAddress = ref('20:37:06:12:34:56');
const details = computed<string | undefined>(() => (db as Record<string, string>)[getVendorValue(macAddress.value)]);
const { copy } = useCopy({ source: () => details.value ?? '', text: 'Vendor info copied to the clipboard' });
const { copy } = useCopy({ source: () => details.value ?? '', text: t('tools.mac-address-lookup.copied') });
</script>
<template>
<div>
<c-input-text
v-model:value="macAddress"
label="MAC address:"
:label="t('tools.mac-address-lookup.MACAddressLabel')"
size="large"
placeholder="Type a MAC address"
:placeholder="t('tools.mac-address-lookup.MACAddressPlaceholder')"
clearable
autocomplete="off"
autocorrect="off"
@ -28,7 +29,7 @@ const { copy } = useCopy({ source: () => details.value ?? '', text: 'Vendor info
/>
<div mb-5px>
Vendor info:
{{ t('tools.mac-address-lookup.vendorInfo') }}
</div>
<c-card mb-5>
<div v-if="details">
@ -38,13 +39,13 @@ const { copy } = useCopy({ source: () => details.value ?? '', text: 'Vendor info
</div>
<div v-else italic op-60>
Unknown vendor for this address
{{ t('tools.mac-address-lookup.unknownAddress') }}
</div>
</c-card>
<div flex justify-center>
<c-button :disabled="!details" @click="copy()">
Copy vendor info
{{ t('tools.mac-address-lookup.copyBtn') }}
</c-button>
</div>
</div>

View file

@ -1,10 +1,11 @@
import { Math } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Math evaluator',
name: t('tools.math-evaluator.title'),
path: '/math-evaluator',
description: 'A calculator for evaluating mathematical expressions. You can use functions like sqrt, cos, sin, abs, etc.',
description: t('tools.math-evaluator.description'),
keywords: [
'math',
'evaluator',

View file

@ -0,0 +1,7 @@
tools:
math-evaluator:
title: Math evaluator
description: A calculator for evaluating mathematical expressions. You can use functions like sqrt, cos, sin, abs, etc.
inputPlaceholder: 'Your math expression (ex: 2*sqrt(6) )...'
result: Result

View file

@ -0,0 +1,7 @@
tools:
math-evaluator:
title: 数学表达式求值器
description: 用于计算数学表达式的计算器。您可以使用函数如sqrtcossinabs等。
inputPlaceholder: '您的数学表达式例如2*sqrt(6)...'
result: 结果

View file

@ -3,6 +3,7 @@ import { evaluate } from 'mathjs';
import { withDefaultOnError } from '@/utils/defaults';
const { t } = useI18n();
const expression = ref('');
const result = computed(() => withDefaultOnError(() => evaluate(expression.value) ?? '', ''));
@ -14,14 +15,14 @@ const result = computed(() => withDefaultOnError(() => evaluate(expression.value
v-model:value="expression"
rows="1"
multiline
placeholder="Your math expression (ex: 2*sqrt(6) )..."
:placeholder="t('tools.math-evaluator.inputPlaceholder')"
raw-text
monospace
autofocus
autosize
/>
<c-card v-if="result !== ''" title="Result " mt-5>
<c-card v-if="result !== ''" :title="t('tools.math-evaluator.result')" mt-5>
{{ result }}
</c-card>
</div>

View file

@ -1,10 +1,11 @@
import { Percentage } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate as t } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Percentage calculator',
name: t('tools.percentage-calculator.title'),
path: '/percentage-calculator',
description: 'Easily calculate percentages from a value to another value, or from a percentage to a value.',
description: t('tools.percentage-calculator.description'),
keywords: ['percentage', 'calculator', 'calculate', 'value', 'number', '%'],
component: () => import('./percentage-calculator.vue'),
icon: Percentage,

View file

@ -0,0 +1,13 @@
tools:
percentage-calculator:
title: Percentage calculator
description: Easily calculate percentages from a value to another value, or from a percentage to a value.
whatIs: What is
percentageX: '% of'
XIsWhatPercentOfY: X is what percent of Y
percentOf: is what percent of
percentage: What is the percentage increase/decrease
from: From
to: To
result: Result

View file

@ -0,0 +1,13 @@
tools:
percentage-calculator:
title: 百分比计算器
description: 轻松计算从一个值到另一个值的百分比,或从一个百分比到一个值。
whatIs: 什么是
percentageX: '% 对于'
XIsWhatPercentOfY: X 是 Y 的百分之多少
percentOf: 是百分之几
percentage: 百分比的增长/减少是多少
from:
to:
result: 结果

View file

@ -1,4 +1,5 @@
<script setup lang="ts">
const { t } = useI18n();
const percentageX = ref();
const percentageY = ref();
const percentageResult = computed(() => {
@ -34,43 +35,43 @@ const percentageIncreaseDecrease = computed(() => {
<div style="margin: 0 auto; max-width: 600px">
<c-card mb-3>
<div mb-3 sm:hidden>
What is
{{ t('tools.percentage-calculator.whatIs') }}
</div>
<div flex gap-2>
<div hidden pt-1 sm:block style="min-width: 48px;">
What is
{{ t('tools.percentage-calculator.whatIs') }}
</div>
<n-input-number v-model:value="percentageX" data-test-id="percentageX" placeholder="X" />
<div min-w-fit pt-1>
% of
{{ t('tools.percentage-calculator.percentageX') }}
</div>
<n-input-number v-model:value="percentageY" data-test-id="percentageY" placeholder="Y" />
<input-copyable v-model:value="percentageResult" data-test-id="percentageResult" readonly placeholder="Result" style="max-width: 150px;" />
<input-copyable v-model:value="percentageResult" data-test-id="percentageResult" readonly :placeholder="t('tools.percentage-calculator.result')" style="max-width: 150px;" />
</div>
</c-card>
<c-card mb-3>
<div mb-3 sm:hidden>
X is what percent of Y
{{ t('tools.percentage-calculator.XIsWhatPercentOfY') }}
</div>
<div flex gap-2>
<n-input-number v-model:value="numberX" data-test-id="numberX" placeholder="X" />
<div hidden min-w-fit pt-1 sm:block>
is what percent of
{{ t('tools.percentage-calculator.percentOf') }}
</div>
<n-input-number v-model:value="numberY" data-test-id="numberY" placeholder="Y" />
<input-copyable v-model:value="numberResult" data-test-id="numberResult" readonly placeholder="Result" style="max-width: 150px;" />
<input-copyable v-model:value="numberResult" data-test-id="numberResult" readonly :placeholder="t('tools.percentage-calculator.result')" style="max-width: 150px;" />
</div>
</c-card>
<c-card mb-3>
<div mb-3>
What is the percentage increase/decrease
{{ t('tools.percentage-calculator.percentage') }}
</div>
<div flex gap-2>
<n-input-number v-model:value="numberFrom" data-test-id="numberFrom" placeholder="From" />
<n-input-number v-model:value="numberTo" data-test-id="numberTo" placeholder="To" />
<input-copyable v-model:value="percentageIncreaseDecrease" data-test-id="percentageIncreaseDecrease" readonly placeholder="Result" style="max-width: 150px;" />
<n-input-number v-model:value="numberFrom" data-test-id="numberFrom" :placeholder="t('tools.percentage-calculator.from')" />
<n-input-number v-model:value="numberTo" data-test-id="numberTo" :placeholder="t('tools.percentage-calculator.to')" />
<input-copyable v-model:value="percentageIncreaseDecrease" data-test-id="percentageIncreaseDecrease" readonly :placeholder="t('tools.percentage-calculator.result')" style="max-width: 150px;" />
</div>
</c-card>
</div>