mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-23 00:53:08 +02:00
refactor: deprecate i18n.t (#13039)
* refactor: deprecate i18n.t * revert: deprecate i18n.t This reverts commit 7dbf873a2f745040ee723df5db659acacff84e12. * chore: reimpl
This commit is contained in:
parent
a637b4e282
commit
7881f06be0
84 changed files with 7311 additions and 222 deletions
|
@ -16,32 +16,40 @@ function createMemberType(item) {
|
||||||
item.matchAll(parameterRegExp),
|
item.matchAll(parameterRegExp),
|
||||||
([, parameter]) => parameter,
|
([, parameter]) => parameter,
|
||||||
);
|
);
|
||||||
if (!parameters.length) {
|
return parameters.length
|
||||||
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
? ts.factory.createTypeReferenceNode(
|
||||||
}
|
|
||||||
return ts.factory.createTypeReferenceNode(
|
|
||||||
ts.factory.createIdentifier('ParameterizedString'),
|
ts.factory.createIdentifier('ParameterizedString'),
|
||||||
[
|
[
|
||||||
ts.factory.createUnionTypeNode(
|
ts.factory.createUnionTypeNode(
|
||||||
parameters.map((parameter) =>
|
parameters.map((parameter) =>
|
||||||
ts.factory.createLiteralTypeNode(
|
|
||||||
ts.factory.createStringLiteral(parameter),
|
ts.factory.createStringLiteral(parameter),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
)
|
||||||
|
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMembers(record) {
|
function createMembers(record) {
|
||||||
return Object.entries(record).map(([k, v]) =>
|
return Object.entries(record).map(([k, v]) => {
|
||||||
ts.factory.createPropertySignature(
|
const node = ts.factory.createPropertySignature(
|
||||||
undefined,
|
undefined,
|
||||||
ts.factory.createStringLiteral(k),
|
ts.factory.createStringLiteral(k),
|
||||||
undefined,
|
undefined,
|
||||||
createMemberType(v),
|
createMemberType(v),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
node,
|
||||||
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
||||||
|
`*
|
||||||
|
* ${v.replace(/\n/g, '\n * ')}
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function generateDTS() {
|
export default function generateDTS() {
|
||||||
|
@ -72,10 +80,8 @@ export default function generateDTS() {
|
||||||
ts.factory.createTypeParameterDeclaration(
|
ts.factory.createTypeParameterDeclaration(
|
||||||
undefined,
|
undefined,
|
||||||
ts.factory.createIdentifier('T'),
|
ts.factory.createIdentifier('T'),
|
||||||
ts.factory.createTypeReferenceNode(
|
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||||
ts.factory.createIdentifier('string'),
|
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -115,7 +121,6 @@ export default function generateDTS() {
|
||||||
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
||||||
ts.factory.createTypeReferenceNode(
|
ts.factory.createTypeReferenceNode(
|
||||||
ts.factory.createIdentifier('ParameterizedString'),
|
ts.factory.createIdentifier('ParameterizedString'),
|
||||||
[ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)],
|
|
||||||
),
|
),
|
||||||
ts.factory.createTypeReferenceNode(
|
ts.factory.createTypeReferenceNode(
|
||||||
ts.factory.createIdentifier('ILocale'),
|
ts.factory.createIdentifier('ILocale'),
|
||||||
|
@ -187,6 +192,24 @@ export default function generateDTS() {
|
||||||
),
|
),
|
||||||
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
|
ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
|
||||||
];
|
];
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
elements[0],
|
||||||
|
ts.SyntaxKind.MultiLineCommentTrivia,
|
||||||
|
' eslint-disable ',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
elements[0],
|
||||||
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
||||||
|
' This file is generated by locales/generateDTS.js',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
ts.addSyntheticLeadingComment(
|
||||||
|
elements[0],
|
||||||
|
ts.SyntaxKind.SingleLineCommentTrivia,
|
||||||
|
' Do not edit this file directly.',
|
||||||
|
true,
|
||||||
|
);
|
||||||
const printed = ts
|
const printed = ts
|
||||||
.createPrinter({
|
.createPrinter({
|
||||||
newLine: ts.NewLineKind.LineFeed,
|
newLine: ts.NewLineKind.LineFeed,
|
||||||
|
@ -203,12 +226,5 @@ export default function generateDTS() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(`${__dirname}/index.d.ts`, printed, 'utf-8');
|
||||||
`${__dirname}/index.d.ts`,
|
|
||||||
`/* eslint-disable */
|
|
||||||
// This file is generated by locales/generateDTS.js
|
|
||||||
// Do not edit this file directly.
|
|
||||||
${printed}`,
|
|
||||||
'utf-8',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
6878
locales/index.d.ts
vendored
6878
locales/index.d.ts
vendored
File diff suppressed because it is too large
Load diff
|
@ -205,7 +205,7 @@ export async function mainBoot() {
|
||||||
const lastUsedDate = parseInt(lastUsed, 10);
|
const lastUsedDate = parseInt(lastUsed, 10);
|
||||||
// 二時間以上前なら
|
// 二時間以上前なら
|
||||||
if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
|
if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
|
||||||
toast(i18n.t('welcomeBackWithName', {
|
toast(i18n.tsx.welcomeBackWithName({
|
||||||
name: $i.name || $i.username,
|
name: $i.name || $i.username,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ async function ok() {
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
title: i18n.ts._announcement.readConfirmTitle,
|
title: i18n.ts._announcement.readConfirmTitle,
|
||||||
text: i18n.t('_announcement.readConfirmText', { title: props.announcement.title }),
|
text: i18n.tsx._announcement.readConfirmText({ title: props.announcement.title }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,9 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const label = computed(() => {
|
const label = computed(() => {
|
||||||
return concat([
|
return concat([
|
||||||
props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [],
|
props.text ? [i18n.tsx._cw.chars({ count: props.text.length })] : [],
|
||||||
props.renote ? [i18n.ts.quote] : [],
|
props.renote ? [i18n.ts.quote] : [],
|
||||||
props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [],
|
props.files.length !== 0 ? [i18n.tsx._cw.files({ count: props.files.length })] : [],
|
||||||
props.poll != null ? [i18n.ts.poll] : [],
|
props.poll != null ? [i18n.ts.poll] : [],
|
||||||
] as string[][]).join(' / ');
|
] as string[][]).join(' / ');
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default defineComponent({
|
||||||
function getDateText(time: string) {
|
function getDateText(time: string) {
|
||||||
const date = new Date(time).getDate();
|
const date = new Date(time).getDate();
|
||||||
const month = new Date(time).getMonth() + 1;
|
const month = new Date(time).getMonth() + 1;
|
||||||
return i18n.t('monthAndDay', {
|
return i18n.tsx.monthAndDay({
|
||||||
month: month.toString(),
|
month: month.toString(),
|
||||||
day: date.toString(),
|
day: date.toString(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,8 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
|
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
|
||||||
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
|
<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.tsx._dialog.charactersExceeded({ current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
|
||||||
<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
|
<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.tsx._dialog.charactersBelow({ current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
|
||||||
</template>
|
</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<MkSelect v-if="select" v-model="selectedValue" autofocus>
|
<MkSelect v-if="select" v-model="selectedValue" autofocus>
|
||||||
|
|
|
@ -82,8 +82,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.ts.loadMore }}</MkButton>
|
<MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.ts.loadMore }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="files.length == 0 && folders.length == 0 && !fetching" :class="$style.empty">
|
<div v-if="files.length == 0 && folders.length == 0 && !fetching" :class="$style.empty">
|
||||||
<div v-if="draghover">{{ i18n.t('empty-draghover') }}</div>
|
<div v-if="draghover">{{ i18n.ts['empty-draghover'] }}</div>
|
||||||
<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</div>
|
<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.ts['empty-drive-description'] }}</div>
|
||||||
<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
|
<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -84,7 +84,7 @@ async function onClick() {
|
||||||
if (isFollowing.value) {
|
if (isFollowing.value) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }),
|
text: i18n.tsx.unfollowConfirm({ name: props.user.name || props.user.username }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
|
@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="translating || translation" :class="$style.translation">
|
<div v-if="translating || translation" :class="$style.translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="translating || translation" :class="$style.translation">
|
<div v-if="translating || translation" :class="$style.translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
|
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,8 +56,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
|
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
|
||||||
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
|
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
|
||||||
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
|
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
|
||||||
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.t('_notification.reactedBySomeUsers', { n: notification.reactions.length }) }}</span>
|
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }}</span>
|
||||||
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.t('_notification.renotedBySomeUsers', { n: notification.users.length }) }}</span>
|
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span>
|
||||||
<span v-else>{{ notification.header }}</span>
|
<span v-else>{{ notification.header }}</span>
|
||||||
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
|
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
|
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
|
||||||
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch>
|
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.ts._notification._types[ntype] }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
|
|
|
@ -11,12 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span :class="$style.fg">
|
<span :class="$style.fg">
|
||||||
<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template>
|
<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template>
|
||||||
<Mfm :text="choice.text" :plain="true"/>
|
<Mfm :text="choice.text" :plain="true"/>
|
||||||
<span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span>
|
<span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-if="!readOnly" :class="$style.info">
|
<p v-if="!readOnly" :class="$style.info">
|
||||||
<span>{{ i18n.t('_poll.totalVotes', { n: total }) }}</span>
|
<span>{{ i18n.tsx._poll.totalVotes({ n: total }) }}</span>
|
||||||
<span> · </span>
|
<span> · </span>
|
||||||
<a v-if="!closed && !isVoted" style="color: inherit;" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a>
|
<a v-if="!closed && !isVoted" style="color: inherit;" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a>
|
||||||
<span v-if="isVoted">{{ i18n.ts._poll.voted }}</span>
|
<span v-if="isVoted">{{ i18n.ts._poll.voted }}</span>
|
||||||
|
@ -47,10 +47,11 @@ const remaining = ref(-1);
|
||||||
const total = computed(() => sum(props.note.poll.choices.map(x => x.votes)));
|
const total = computed(() => sum(props.note.poll.choices.map(x => x.votes)));
|
||||||
const closed = computed(() => remaining.value === 0);
|
const closed = computed(() => remaining.value === 0);
|
||||||
const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted));
|
const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted));
|
||||||
const timer = computed(() => i18n.t(
|
const timer = computed(() => i18n.tsx._poll[
|
||||||
remaining.value >= 86400 ? '_poll.remainingDays' :
|
remaining.value >= 86400 ? 'remainingDays' :
|
||||||
remaining.value >= 3600 ? '_poll.remainingHours' :
|
remaining.value >= 3600 ? 'remainingHours' :
|
||||||
remaining.value >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', {
|
remaining.value >= 60 ? 'remainingMinutes' : 'remainingSeconds'
|
||||||
|
]({
|
||||||
s: Math.floor(remaining.value % 60),
|
s: Math.floor(remaining.value % 60),
|
||||||
m: Math.floor(remaining.value / 60) % 60,
|
m: Math.floor(remaining.value / 60) % 60,
|
||||||
h: Math.floor(remaining.value / 3600) % 24,
|
h: Math.floor(remaining.value / 3600) % 24,
|
||||||
|
@ -81,7 +82,7 @@ const vote = async (id) => {
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
text: i18n.t('voteConfirm', { choice: props.note.poll.choices[id].text }),
|
text: i18n.tsx.voteConfirm({ choice: props.note.poll.choices[id].text }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(choice, i) in choices" :key="i">
|
<li v-for="(choice, i) in choices" :key="i">
|
||||||
<MkInput class="input" small :modelValue="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)">
|
<MkInput class="input" small :modelValue="choice" :placeholder="i18n.tsx._poll.choiceN({ n: i + 1 })" @update:modelValue="onInput(i, $event)">
|
||||||
</MkInput>
|
</MkInput>
|
||||||
<button class="_button" @click="remove(i)">
|
<button class="_button" @click="remove(i)">
|
||||||
<i class="ti ti-x"></i>
|
<i class="ti ti-x"></i>
|
||||||
|
|
|
@ -263,7 +263,7 @@ async function onSubmit(): Promise<void> {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: i18n.ts._signup.almostThere,
|
title: i18n.ts._signup.almostThere,
|
||||||
text: i18n.t('_signup.emailSent', { email: email.value }),
|
text: i18n.tsx._signup.emailSent({ email: email.value }),
|
||||||
});
|
});
|
||||||
emit('signupEmailPending');
|
emit('signupEmailPending');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -105,7 +105,7 @@ async function updateAgreeServerRules(v: boolean) {
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
title: i18n.ts.doYouAgree,
|
title: i18n.ts.doYouAgree,
|
||||||
text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.serverRules }),
|
text: i18n.tsx.iHaveReadXCarefullyAndAgree({ x: i18n.ts.serverRules }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
agreeServerRules.value = true;
|
agreeServerRules.value = true;
|
||||||
|
@ -119,7 +119,7 @@ async function updateAgreeTosAndPrivacyPolicy(v: boolean) {
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
title: i18n.ts.doYouAgree,
|
title: i18n.ts.doYouAgree,
|
||||||
text: i18n.t('iHaveReadXCarefullyAndAgree', {
|
text: i18n.tsx.iHaveReadXCarefullyAndAgree({
|
||||||
x: tosPrivacyPolicyLabel.value,
|
x: tosPrivacyPolicyLabel.value,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -135,7 +135,7 @@ async function updateAgreeNote(v: boolean) {
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
title: i18n.ts.doYouAgree,
|
title: i18n.ts.doYouAgree,
|
||||||
text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.basicNotesBeforeCreateAccount }),
|
text: i18n.tsx.iHaveReadXCarefullyAndAgree({ x: i18n.ts.basicNotesBeforeCreateAccount }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
agreeNote.value = true;
|
agreeNote.value = true;
|
||||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||||
</div>
|
</div>
|
||||||
<details v-if="note.files.length > 0">
|
<details v-if="note.files.length > 0">
|
||||||
<summary>({{ i18n.t('withNFiles', { n: note.files.length }) }})</summary>
|
<summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary>
|
||||||
<MkMediaList :mediaList="note.files"/>
|
<MkMediaList :mediaList="note.files"/>
|
||||||
</details>
|
</details>
|
||||||
<details v-if="note.poll">
|
<details v-if="note.poll">
|
||||||
|
|
|
@ -33,12 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkSwitch v-for="kind in Object.keys(permissionSwitches)" :key="kind" v-model="permissionSwitches[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
|
<MkSwitch v-for="kind in Object.keys(permissionSwitches)" :key="kind" v-model="permissionSwitches[kind]">{{ i18n.ts._permissions[kind] }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="iAmAdmin" :class="$style.adminPermissions">
|
<div v-if="iAmAdmin" :class="$style.adminPermissions">
|
||||||
<div :class="$style.adminPermissionsHeader"><b>{{ i18n.ts.adminPermission }}</b></div>
|
<div :class="$style.adminPermissionsHeader"><b>{{ i18n.ts.adminPermission }}</b></div>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkSwitch v-for="kind in Object.keys(permissionSwitchesForAdmin)" :key="kind" v-model="permissionSwitchesForAdmin[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
|
<MkSwitch v-for="kind in Object.keys(permissionSwitchesForAdmin)" :key="kind" v-model="permissionSwitchesForAdmin[kind]">{{ i18n.ts._permissions[kind] }}</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<a href="https://misskey-hub.net/docs/for-users/" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
<a href="https://misskey-hub.net/docs/for-users/" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
|
<div>{{ i18n.tsx._initialAccountSetting.haveFun({ name: instance.name ?? host }) }}</div>
|
||||||
<div class="_buttonsCenter" style="margin-top: 16px;">
|
<div class="_buttonsCenter" style="margin-top: 16px;">
|
||||||
<MkButton v-if="initialPage !== 4" rounded @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
<MkButton v-if="initialPage !== 4" rounded @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
<MkButton rounded primary gradate @click="close(false)">{{ i18n.ts.close }}</MkButton>
|
<MkButton rounded primary gradate @click="close(false)">{{ i18n.ts.close }}</MkButton>
|
||||||
|
|
|
@ -118,7 +118,7 @@ async function done() {
|
||||||
async function del() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('removeAreYouSure', { x: title.value }),
|
text: i18n.tsx.removeAreYouSure({ x: title.value }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ function setAvatar(ev) {
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
text: i18n.t('cropImageAsk'),
|
text: i18n.ts.cropImageAsk,
|
||||||
okText: i18n.ts.cropYes,
|
okText: i18n.ts.cropYes,
|
||||||
cancelText: i18n.ts.cropNo,
|
cancelText: i18n.ts.cropNo,
|
||||||
});
|
});
|
||||||
|
|
|
@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps" style="text-align: center;">
|
<div class="_gaps" style="text-align: center;">
|
||||||
<i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
|
<i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
|
||||||
<div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div>
|
<div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div>
|
||||||
<div style="padding: 0 16px;">{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}</div>
|
<div style="padding: 0 16px;">{{ i18n.tsx._initialAccountSetting.pushNotificationDescription({ name: instance.name ?? host }) }}</div>
|
||||||
<MkPushNotificationAllowButton primary showOnlyToRegister style="margin: 0 auto;"/>
|
<MkPushNotificationAllowButton primary showOnlyToRegister style="margin: 0 auto;"/>
|
||||||
<div class="_buttonsCenter" style="margin-top: 16px;">
|
<div class="_buttonsCenter" style="margin-top: 16px;">
|
||||||
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
|
||||||
|
@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps" style="text-align: center;">
|
<div class="_gaps" style="text-align: center;">
|
||||||
<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
|
<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
|
||||||
<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
|
<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
|
||||||
<div>{{ i18n.t('_initialAccountSetting.youCanContinueTutorial', { name: instance.name ?? host }) }}</div>
|
<div>{{ i18n.tsx._initialAccountSetting.youCanContinueTutorial({ name: instance.name ?? host }) }}</div>
|
||||||
<div class="_buttonsCenter" style="margin-top: 16px;">
|
<div class="_buttonsCenter" style="margin-top: 16px;">
|
||||||
<MkButton rounded primary gradate data-cy-user-setup-continue @click="launchTutorial()">{{ i18n.ts._initialAccountSetting.startTutorial }} <i class="ti ti-arrow-right"></i></MkButton>
|
<MkButton rounded primary gradate data-cy-user-setup-continue @click="launchTutorial()">{{ i18n.ts._initialAccountSetting.startTutorial }} <i class="ti ti-arrow-right"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<header :class="$style.editHeader">
|
<header :class="$style.editHeader">
|
||||||
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
|
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
|
||||||
<template #label>{{ i18n.ts.selectWidget }}</template>
|
<template #label>{{ i18n.ts.selectWidget }}</template>
|
||||||
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option>
|
<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
|
<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
|
||||||
|
@ -109,7 +109,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
|
||||||
|
|
||||||
os.contextMenu([{
|
os.contextMenu([{
|
||||||
type: 'label',
|
type: 'label',
|
||||||
text: i18n.t(`_widgets.${widget.name}`),
|
text: i18n.ts._widgets[widget.name],
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-settings',
|
icon: 'ti ti-settings',
|
||||||
text: i18n.ts.settings,
|
text: i18n.ts.settings,
|
||||||
|
|
46
packages/frontend/src/components/global/I18n.vue
Normal file
46
packages/frontend/src/components/global/I18n.vue
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<render/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" generic="T extends string | ParameterizedString">
|
||||||
|
import { computed, h } from 'vue';
|
||||||
|
import type { ParameterizedString } from '../../../../../locales/index.js';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
src: T;
|
||||||
|
tag?: string;
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
textTag?: string;
|
||||||
|
}>(), {
|
||||||
|
tag: 'span',
|
||||||
|
});
|
||||||
|
|
||||||
|
const slots = defineSlots<T extends ParameterizedString<infer R> ? { [K in R]: () => unknown } : NonNullable<unknown>>();
|
||||||
|
|
||||||
|
const parsed = computed(() => {
|
||||||
|
let str = props.src as string;
|
||||||
|
const value: (string | { arg: string; })[] = [];
|
||||||
|
for (;;) {
|
||||||
|
const nextBracketOpen = str.indexOf('{');
|
||||||
|
const nextBracketClose = str.indexOf('}');
|
||||||
|
|
||||||
|
if (nextBracketOpen === -1) {
|
||||||
|
value.push(str);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if (nextBracketOpen > 0) value.push(str.substring(0, nextBracketOpen));
|
||||||
|
value.push({
|
||||||
|
arg: str.substring(nextBracketOpen + 1, nextBracketClose),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.substring(nextBracketClose + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]()));
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -123,7 +123,7 @@ export const DetailNow = {
|
||||||
export const RelativeOneHourAgo = {
|
export const RelativeOneHourAgo = {
|
||||||
...Empty,
|
...Empty,
|
||||||
async play({ canvasElement }) {
|
async play({ canvasElement }) {
|
||||||
await expect(canvasElement).toHaveTextContent(i18n.t('_ago.hoursAgo', { n: 1 }));
|
await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.hoursAgo({ n: 1 }));
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
...Empty.args,
|
...Empty.args,
|
||||||
|
@ -162,7 +162,7 @@ export const DetailOneHourAgo = {
|
||||||
export const RelativeOneDayAgo = {
|
export const RelativeOneDayAgo = {
|
||||||
...Empty,
|
...Empty,
|
||||||
async play({ canvasElement }) {
|
async play({ canvasElement }) {
|
||||||
await expect(canvasElement).toHaveTextContent(i18n.t('_ago.daysAgo', { n: 1 }));
|
await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.daysAgo({ n: 1 }));
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
...Empty.args,
|
...Empty.args,
|
||||||
|
@ -201,7 +201,7 @@ export const DetailOneDayAgo = {
|
||||||
export const RelativeOneWeekAgo = {
|
export const RelativeOneWeekAgo = {
|
||||||
...Empty,
|
...Empty,
|
||||||
async play({ canvasElement }) {
|
async play({ canvasElement }) {
|
||||||
await expect(canvasElement).toHaveTextContent(i18n.t('_ago.weeksAgo', { n: 1 }));
|
await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.weeksAgo({ n: 1 }));
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
...Empty.args,
|
...Empty.args,
|
||||||
|
@ -240,7 +240,7 @@ export const DetailOneWeekAgo = {
|
||||||
export const RelativeOneMonthAgo = {
|
export const RelativeOneMonthAgo = {
|
||||||
...Empty,
|
...Empty,
|
||||||
async play({ canvasElement }) {
|
async play({ canvasElement }) {
|
||||||
await expect(canvasElement).toHaveTextContent(i18n.t('_ago.monthsAgo', { n: 1 }));
|
await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.monthsAgo({ n: 1 }));
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
...Empty.args,
|
...Empty.args,
|
||||||
|
@ -279,7 +279,7 @@ export const DetailOneMonthAgo = {
|
||||||
export const RelativeOneYearAgo = {
|
export const RelativeOneYearAgo = {
|
||||||
...Empty,
|
...Empty,
|
||||||
async play({ canvasElement }) {
|
async play({ canvasElement }) {
|
||||||
await expect(canvasElement).toHaveTextContent(i18n.t('_ago.yearsAgo', { n: 1 }));
|
await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.yearsAgo({ n: 1 }));
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
...Empty.args,
|
...Empty.args,
|
||||||
|
|
|
@ -55,21 +55,21 @@ const relative = computed<string>(() => {
|
||||||
if (invalid) return i18n.ts._ago.invalid;
|
if (invalid) return i18n.ts._ago.invalid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
ago.value >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago.value / 31536000).toString() }) :
|
ago.value >= 31536000 ? i18n.tsx._ago.yearsAgo({ n: Math.round(ago.value / 31536000).toString() }) :
|
||||||
ago.value >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago.value / 2592000).toString() }) :
|
ago.value >= 2592000 ? i18n.tsx._ago.monthsAgo({ n: Math.round(ago.value / 2592000).toString() }) :
|
||||||
ago.value >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago.value / 604800).toString() }) :
|
ago.value >= 604800 ? i18n.tsx._ago.weeksAgo({ n: Math.round(ago.value / 604800).toString() }) :
|
||||||
ago.value >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago.value / 86400).toString() }) :
|
ago.value >= 86400 ? i18n.tsx._ago.daysAgo({ n: Math.round(ago.value / 86400).toString() }) :
|
||||||
ago.value >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago.value / 3600).toString() }) :
|
ago.value >= 3600 ? i18n.tsx._ago.hoursAgo({ n: Math.round(ago.value / 3600).toString() }) :
|
||||||
ago.value >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago.value / 60)).toString() }) :
|
ago.value >= 60 ? i18n.tsx._ago.minutesAgo({ n: (~~(ago.value / 60)).toString() }) :
|
||||||
ago.value >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago.value % 60)).toString() }) :
|
ago.value >= 10 ? i18n.tsx._ago.secondsAgo({ n: (~~(ago.value % 60)).toString() }) :
|
||||||
ago.value >= -3 ? i18n.ts._ago.justNow :
|
ago.value >= -3 ? i18n.ts._ago.justNow :
|
||||||
ago.value < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago.value / 31536000).toString() }) :
|
ago.value < -31536000 ? i18n.tsx._timeIn.years({ n: Math.round(-ago.value / 31536000).toString() }) :
|
||||||
ago.value < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago.value / 2592000).toString() }) :
|
ago.value < -2592000 ? i18n.tsx._timeIn.months({ n: Math.round(-ago.value / 2592000).toString() }) :
|
||||||
ago.value < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago.value / 604800).toString() }) :
|
ago.value < -604800 ? i18n.tsx._timeIn.weeks({ n: Math.round(-ago.value / 604800).toString() }) :
|
||||||
ago.value < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago.value / 86400).toString() }) :
|
ago.value < -86400 ? i18n.tsx._timeIn.days({ n: Math.round(-ago.value / 86400).toString() }) :
|
||||||
ago.value < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago.value / 3600).toString() }) :
|
ago.value < -3600 ? i18n.tsx._timeIn.hours({ n: Math.round(-ago.value / 3600).toString() }) :
|
||||||
ago.value < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago.value / 60)).toString() }) :
|
ago.value < -60 ? i18n.tsx._timeIn.minutes({ n: (~~(-ago.value / 60)).toString() }) :
|
||||||
i18n.t('_timeIn.seconds', { n: (~~(-ago.value % 60)).toString() })
|
i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { h } from 'vue';
|
|
||||||
|
|
||||||
export default function(props: { src: string; tag?: string; textTag?: string; }, { slots }) {
|
|
||||||
let str = props.src;
|
|
||||||
const parsed = [] as (string | { arg: string; })[];
|
|
||||||
while (true) {
|
|
||||||
const nextBracketOpen = str.indexOf('{');
|
|
||||||
const nextBracketClose = str.indexOf('}');
|
|
||||||
|
|
||||||
if (nextBracketOpen === -1) {
|
|
||||||
parsed.push(str);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if (nextBracketOpen > 0) parsed.push(str.substring(0, nextBracketOpen));
|
|
||||||
parsed.push({
|
|
||||||
arg: str.substring(nextBracketOpen + 1, nextBracketClose),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
str = str.substring(nextBracketClose + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return h(props.tag ?? 'span', parsed.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]()));
|
|
||||||
}
|
|
|
@ -16,7 +16,7 @@ import MkUserName from './global/MkUserName.vue';
|
||||||
import MkEllipsis from './global/MkEllipsis.vue';
|
import MkEllipsis from './global/MkEllipsis.vue';
|
||||||
import MkTime from './global/MkTime.vue';
|
import MkTime from './global/MkTime.vue';
|
||||||
import MkUrl from './global/MkUrl.vue';
|
import MkUrl from './global/MkUrl.vue';
|
||||||
import I18n from './global/i18n.js';
|
import I18n from './global/I18n.vue';
|
||||||
import RouterView from './global/RouterView.vue';
|
import RouterView from './global/RouterView.vue';
|
||||||
import MkLoading from './global/MkLoading.vue';
|
import MkLoading from './global/MkLoading.vue';
|
||||||
import MkError from './global/MkError.vue';
|
import MkError from './global/MkError.vue';
|
||||||
|
|
|
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #key>Misskey</template>
|
<template #key>Misskey</template>
|
||||||
<template #value>{{ version }}</template>
|
<template #value>{{ version }}</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<div v-html="i18n.t('poweredByMisskeyDescription', { name: instance.name ?? host })">
|
<div v-html="i18n.tsx.poweredByMisskeyDescription({ name: instance.name ?? host })">
|
||||||
</div>
|
</div>
|
||||||
<FormLink to="/about-misskey">{{ i18n.ts.aboutMisskey }}</FormLink>
|
<FormLink to="/about-misskey">{{ i18n.ts.aboutMisskey }}</FormLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -104,7 +104,7 @@ fetch();
|
||||||
async function del() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('removeAreYouSure', { x: file.value.name }),
|
text: i18n.tsx.removeAreYouSure({ x: file.value.name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -182,9 +182,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</div>
|
</div>
|
||||||
<div class="charts">
|
<div class="charts">
|
||||||
<div class="label">{{ i18n.t('recentNHours', { n: 90 }) }}</div>
|
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
||||||
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||||
<div class="label">{{ i18n.t('recentNDays', { n: 90 }) }}</div>
|
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
||||||
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -307,7 +307,7 @@ async function resetPassword() {
|
||||||
});
|
});
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: i18n.t('newPasswordIs', { password }),
|
text: i18n.tsx.newPasswordIs({ password }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,7 +390,7 @@ async function deleteAccount() {
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
|
|
||||||
const typed = await os.inputText({
|
const typed = await os.inputText({
|
||||||
text: i18n.t('typeToConfirm', { x: user.value?.username }),
|
text: i18n.tsx.typeToConfirm({ x: user.value?.username }),
|
||||||
});
|
});
|
||||||
if (typed.canceled) return;
|
if (typed.canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -160,7 +160,7 @@ function add() {
|
||||||
function remove(ad) {
|
function remove(ad) {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('removeAreYouSure', { x: ad.url }),
|
text: i18n.tsx.removeAreYouSure({ x: ad.url }),
|
||||||
}).then(({ canceled }) => {
|
}).then(({ canceled }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
ads.value = ads.value.filter(x => x !== ad);
|
ads.value = ads.value.filter(x => x !== ad);
|
||||||
|
|
|
@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
|
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
|
||||||
{{ i18n.ts._announcement.needConfirmationToRead }}
|
{{ i18n.ts._announcement.needConfirmationToRead }}
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
<p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p>
|
<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
|
||||||
<div class="buttons _buttons">
|
<div class="buttons _buttons">
|
||||||
<MkButton class="button" inline primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
<MkButton class="button" inline primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
<MkButton v-if="announcement.id != null" class="button" inline @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
|
<MkButton v-if="announcement.id != null" class="button" inline @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
|
||||||
|
@ -109,7 +109,7 @@ function add() {
|
||||||
function del(announcement) {
|
function del(announcement) {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('deleteAreYouSure', { x: announcement.title }),
|
text: i18n.tsx.deleteAreYouSure({ x: announcement.title }),
|
||||||
}).then(({ canceled }) => {
|
}).then(({ canceled }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
announcements.value = announcements.value.filter(x => x !== announcement);
|
announcements.value = announcements.value.filter(x => x !== announcement);
|
||||||
|
|
|
@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #prefix><i class="ti ti-link"></i></template>
|
<template #prefix><i class="ti ti-link"></i></template>
|
||||||
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</template>
|
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</template>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<div>{{ i18n.t('_serverSettings.appIconDescription', { host: instance.name ?? host }) }}</div>
|
<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
|
||||||
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
||||||
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
||||||
<div><strong>{{ i18n.t('_serverSettings.appIconResolutionMustBe', { resolution: '192x192px' }) }}</strong></div>
|
<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '192x192px' }) }}</strong></div>
|
||||||
</template>
|
</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
|
@ -30,10 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #prefix><i class="ti ti-link"></i></template>
|
<template #prefix><i class="ti ti-link"></i></template>
|
||||||
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</template>
|
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</template>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
<div>{{ i18n.t('_serverSettings.appIconDescription', { host: instance.name ?? host }) }}</div>
|
<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
|
||||||
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
|
||||||
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
|
||||||
<div><strong>{{ i18n.t('_serverSettings.appIconResolutionMustBe', { resolution: '512x512px' }) }}</strong></div>
|
<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '512x512px' }) }}</strong></div>
|
||||||
</template>
|
</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--success);"></i>
|
<i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--success);"></i>
|
||||||
<i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--error);"></i>
|
<i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--error);"></i>
|
||||||
<i v-else class="ti ti-clock" :class="$style.icon"></i>
|
<i v-else class="ti ti-clock" :class="$style.icon"></i>
|
||||||
<span>{{ i18n.t(`_relayStatus.${relay.status}`) }}</span>
|
<span>{{ i18n.ts._relayStatus[relay.status] }}</span>
|
||||||
</div>
|
</div>
|
||||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -104,7 +104,7 @@ function edit() {
|
||||||
async function del() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('deleteAreYouSure', { x: role.name }),
|
text: i18n.tsx.deleteAreYouSure({ x: role.name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ async function read(announcement) {
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
title: i18n.ts._announcement.readConfirmTitle,
|
title: i18n.ts._announcement.readConfirmTitle,
|
||||||
text: i18n.t('_announcement.readConfirmText', { title: announcement.title }),
|
text: i18n.tsx._announcement.readConfirmText({ title: announcement.title }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<div v-if="app.permission.length > 0">
|
<div v-if="app.permission.length > 0">
|
||||||
<p>{{ i18n.t('_auth.permission', { name }) }}</p>
|
<p>{{ i18n.tsx._auth.permission({ name }) }}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="p in app.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
<li v-for="p in app.permission" :key="p">{{ i18n.ts._permissions[p] }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ i18n.t('_auth.shareAccess', { name: `${name} (${app.id})` }) }}</div>
|
<div>{{ i18n.tsx._auth.shareAccess({ name: `${name} (${app.id})` }) }}</div>
|
||||||
<div :class="$style.buttons">
|
<div :class="$style.buttons">
|
||||||
<MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
<MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
|
||||||
<MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
|
<MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<h1>{{ i18n.ts._auth.denied }}</h1>
|
<h1>{{ i18n.ts._auth.denied }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="state == 'accepted' && session">
|
<div v-if="state == 'accepted' && session">
|
||||||
<h1>{{ session.app.isAuthorized ? i18n.t('already-authorized') : i18n.ts.allowed }}</h1>
|
<h1>{{ session.app.isAuthorized ? i18n.ts['already-authorized'] : i18n.ts.allowed }}</h1>
|
||||||
<p v-if="session.app.callbackUrl">
|
<p v-if="session.app.callbackUrl">
|
||||||
{{ i18n.ts._auth.callback }}
|
{{ i18n.ts._auth.callback }}
|
||||||
<MkEllipsis/>
|
<MkEllipsis/>
|
||||||
|
|
|
@ -60,7 +60,7 @@ function add() {
|
||||||
function del(avatarDecoration) {
|
function del(avatarDecoration) {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('deleteAreYouSure', { x: avatarDecoration.name }),
|
text: i18n.tsx.deleteAreYouSure({ x: avatarDecoration.name }),
|
||||||
}).then(({ canceled }) => {
|
}).then(({ canceled }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
|
avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
|
||||||
|
|
|
@ -174,7 +174,7 @@ function save() {
|
||||||
async function archive() {
|
async function archive() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
title: i18n.t('channelArchiveConfirmTitle', { name: name.value }),
|
title: i18n.tsx.channelArchiveConfirmTitle({ name: name.value }),
|
||||||
text: i18n.ts.channelArchiveConfirmDescription,
|
text: i18n.ts.channelArchiveConfirmDescription,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
||||||
handler: async (): Promise<void> => {
|
handler: async (): Promise<void> => {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('deleteAreYouSure', { x: clip.value.name }),
|
text: i18n.tsx.deleteAreYouSure({ x: clip.value.name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -180,7 +180,7 @@ async function deleteFile() {
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('driveFileDeleteConfirm', { name: file.value.name }),
|
text: i18n.tsx.driveFileDeleteConfirm({ name: file.value.name }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.frame">
|
<div :class="$style.frame">
|
||||||
<div :class="$style.frameInner">
|
<div :class="$style.frameInner">
|
||||||
<div class="_gaps_s" style="padding: 16px;">
|
<div class="_gaps_s" style="padding: 16px;">
|
||||||
<div><b>{{ i18n.t('lastNDays', { n: 7 }) }} {{ i18n.ts.ranking }}</b> ({{ gameMode }})</div>
|
<div><b>{{ i18n.tsx.lastNDays({ n: 7 }) }} {{ i18n.ts.ranking }}</b> ({{ gameMode }})</div>
|
||||||
<div v-if="ranking" class="_gaps_s">
|
<div v-if="ranking" class="_gaps_s">
|
||||||
<div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
|
<div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
|
||||||
<MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
<MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
||||||
|
|
|
@ -185,7 +185,7 @@ async function done() {
|
||||||
async function del() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('removeAreYouSure', { x: name.value }),
|
text: i18n.tsx.removeAreYouSure({ x: name.value }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -438,7 +438,7 @@ function show() {
|
||||||
async function del() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('deleteAreYouSure', { x: flash.value.title }),
|
text: i18n.tsx.deleteAreYouSure({ x: flash.value.title }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { mainRouter } from '@/global/router/main.js';
|
||||||
async function follow(user): Promise<void> {
|
async function follow(user): Promise<void> {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
text: i18n.t('followConfirm', { name: user.name || user.username }),
|
text: i18n.tsx.followConfirm({ name: user.name || user.username }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
|
|
|
@ -95,9 +95,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</div>
|
</div>
|
||||||
<div class="charts">
|
<div class="charts">
|
||||||
<div class="label">{{ i18n.t('recentNHours', { n: 90 }) }}</div>
|
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
||||||
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
||||||
<div class="label">{{ i18n.t('recentNDays', { n: 90 }) }}</div>
|
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
||||||
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MKSpacer>
|
</MKSpacer>
|
||||||
<MkSpacer v-else :contentMax="800">
|
<MkSpacer v-else :contentMax="800">
|
||||||
<div class="_gaps_m" style="text-align: center;">
|
<div class="_gaps_m" style="text-align: center;">
|
||||||
<div v-if="resetCycle && inviteLimit">{{ i18n.t('inviteLimitResetCycle', { time: resetCycle, limit: inviteLimit }) }}</div>
|
<div v-if="resetCycle && inviteLimit">{{ i18n.tsx.inviteLimitResetCycle({ time: resetCycle, limit: inviteLimit }) }}</div>
|
||||||
<MkButton inline primary rounded :disabled="currentInviteLimit !== null && currentInviteLimit <= 0" @click="create"><i class="ti ti-user-plus"></i> {{ i18n.ts.createInviteCode }}</MkButton>
|
<MkButton inline primary rounded :disabled="currentInviteLimit !== null && currentInviteLimit <= 0" @click="create"><i class="ti ti-user-plus"></i> {{ i18n.ts.createInviteCode }}</MkButton>
|
||||||
<div v-if="currentInviteLimit !== null">{{ i18n.t('createLimitRemaining', { limit: currentInviteLimit }) }}</div>
|
<div v-if="currentInviteLimit !== null">{{ i18n.tsx.createLimitRemaining({ limit: currentInviteLimit }) }}</div>
|
||||||
|
|
||||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||||
<template #default="{ items }">
|
<template #default="{ items }">
|
||||||
|
|
|
@ -20,13 +20,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-if="_permissions.length > 0">
|
<div v-if="_permissions.length > 0">
|
||||||
<p v-if="name">{{ i18n.t('_auth.permission', { name }) }}</p>
|
<p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p>
|
||||||
<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
|
<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="p in _permissions" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
<li v-for="p in _permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="name">{{ i18n.t('_auth.shareAccess', { name }) }}</div>
|
<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
|
||||||
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
||||||
<div :class="$style.buttons">
|
<div :class="$style.buttons">
|
||||||
<MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton>
|
<MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton>
|
||||||
|
|
|
@ -116,7 +116,7 @@ async function saveAntenna() {
|
||||||
async function deleteAntenna() {
|
async function deleteAntenna() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('removeAreYouSure', { x: props.antenna.name }),
|
text: i18n.tsx.removeAreYouSure({ x: props.antenna.name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div v-if="items.length > 0" class="_gaps">
|
<div v-if="items.length > 0" class="_gaps">
|
||||||
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
|
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
|
||||||
<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})</span></div>
|
<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})</span></div>
|
||||||
<MkAvatars :userIds="list.userIds" :limit="10"/>
|
<MkAvatars :userIds="list.userIds" :limit="10"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<MkFolder defaultOpen>
|
<MkFolder defaultOpen>
|
||||||
<template #label>{{ i18n.ts.members }}</template>
|
<template #label>{{ i18n.ts.members }}</template>
|
||||||
<template #caption>{{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }}</template>
|
<template #caption>{{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }}</template>
|
||||||
|
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
||||||
|
@ -155,7 +155,7 @@ async function deleteList() {
|
||||||
if (!list.value) return;
|
if (!list.value) return;
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('removeAreYouSure', { x: list.value.name }),
|
text: i18n.tsx.removeAreYouSure({ x: list.value.name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -147,7 +147,7 @@ definePageMetadata(computed(() => note.value ? {
|
||||||
avatar: note.value.user,
|
avatar: note.value.user,
|
||||||
path: `/notes/${note.value.id}`,
|
path: `/notes/${note.value.id}`,
|
||||||
share: {
|
share: {
|
||||||
title: i18n.t('noteOf', { user: note.value.user.name }),
|
title: i18n.tsx.noteOf({ user: note.value.user.name }),
|
||||||
text: note.value.text,
|
text: note.value.text,
|
||||||
},
|
},
|
||||||
} : null));
|
} : null));
|
||||||
|
|
|
@ -51,7 +51,7 @@ const directNotesPagination = {
|
||||||
|
|
||||||
function setFilter(ev) {
|
function setFilter(ev) {
|
||||||
const typeItems = notificationTypes.map(t => ({
|
const typeItems = notificationTypes.map(t => ({
|
||||||
text: i18n.t(`_notification._types.${t}`),
|
text: i18n.ts._notification._types[t],
|
||||||
active: includeTypes.value && includeTypes.value.includes(t),
|
active: includeTypes.value && includeTypes.value.includes(t),
|
||||||
action: () => {
|
action: () => {
|
||||||
includeTypes.value = [t];
|
includeTypes.value = [t];
|
||||||
|
|
|
@ -9,13 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div v-if="$i">
|
<div v-if="$i">
|
||||||
<div v-if="permissions.length > 0">
|
<div v-if="permissions.length > 0">
|
||||||
<p v-if="name">{{ i18n.t('_auth.permission', { name }) }}</p>
|
<p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p>
|
||||||
<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
|
<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="p in permissions" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
<li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="name">{{ i18n.t('_auth.shareAccess', { name }) }}</div>
|
<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
|
||||||
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
|
||||||
<form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post">
|
<form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post">
|
||||||
<input name="login_token" type="hidden" :value="$i.token"/>
|
<input name="login_token" type="hidden" :value="$i.token"/>
|
||||||
|
|
|
@ -175,7 +175,7 @@ function save() {
|
||||||
function del() {
|
function del() {
|
||||||
os.confirm({
|
os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('removeAreYouSure', { x: title.value.trim() }),
|
text: i18n.tsx.removeAreYouSure({ x: title.value.trim() }),
|
||||||
}).then(({ canceled }) => {
|
}).then(({ canceled }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
misskeyApi('pages/delete', {
|
misskeyApi('pages/delete', {
|
||||||
|
|
|
@ -10,17 +10,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<div style="overflow: clip; line-height: 28px;">
|
<div style="overflow: clip; line-height: 28px;">
|
||||||
<div v-if="!iAmPlayer && !game.isEnded && turnUser" class="turn">
|
<div v-if="!iAmPlayer && !game.isEnded && turnUser" class="turn">
|
||||||
<Mfm :key="'turn:' + turnUser.id" :text="i18n.t('_reversi.turnOf', { name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
|
<Mfm :key="'turn:' + turnUser.id" :text="i18n.tsx._reversi.turnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
|
||||||
<MkEllipsis/>
|
<MkEllipsis/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="(logPos !== logs.length) && turnUser" class="turn">
|
<div v-if="(logPos !== logs.length) && turnUser" class="turn">
|
||||||
<Mfm :key="'past-turn-of:' + turnUser.id" :text="i18n.t('_reversi.pastTurnOf', { name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
|
<Mfm :key="'past-turn-of:' + turnUser.id" :text="i18n.tsx._reversi.pastTurnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="iAmPlayer && !game.isEnded && !isMyTurn" class="turn1">{{ i18n.ts._reversi.opponentTurn }}<MkEllipsis/></div>
|
<div v-if="iAmPlayer && !game.isEnded && !isMyTurn" class="turn1">{{ i18n.ts._reversi.opponentTurn }}<MkEllipsis/></div>
|
||||||
<div v-if="iAmPlayer && !game.isEnded && isMyTurn" class="turn2" style="animation: tada 1s linear infinite both;">{{ i18n.ts._reversi.myTurn }}</div>
|
<div v-if="iAmPlayer && !game.isEnded && isMyTurn" class="turn2" style="animation: tada 1s linear infinite both;">{{ i18n.ts._reversi.myTurn }}</div>
|
||||||
<div v-if="game.isEnded && logPos == logs.length" class="result">
|
<div v-if="game.isEnded && logPos == logs.length" class="result">
|
||||||
<template v-if="game.winner">
|
<template v-if="game.winner">
|
||||||
<Mfm :key="'won'" :text="i18n.t('_reversi.won', { name: game.winner.name ?? game.winner.username })" :plain="true" :customEmojis="game.winner.emojis"/>
|
<Mfm :key="'won'" :text="i18n.tsx._reversi.won({ name: game.winner.name ?? game.winner.username })" :plain="true" :customEmojis="game.winner.emojis"/>
|
||||||
<span v-if="game.surrendered != null"> ({{ i18n.ts._reversi.surrendered }})</span>
|
<span v-if="game.surrendered != null"> ({{ i18n.ts._reversi.surrendered }})</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>{{ i18n.ts._reversi.drawn }}</template>
|
<template v-else>{{ i18n.ts._reversi.drawn }}</template>
|
||||||
|
@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status"><b>{{ i18n.t('_reversi.turnCount', { count: logPos }) }}</b> {{ i18n.ts._reversi.black }}:{{ engine.blackCount }} {{ i18n.ts._reversi.white }}:{{ engine.whiteCount }} {{ i18n.ts._reversi.total }}:{{ engine.blackCount + engine.whiteCount }}</div>
|
<div class="status"><b>{{ i18n.tsx._reversi.turnCount({ count: logPos }) }}</b> {{ i18n.ts._reversi.black }}:{{ engine.blackCount }} {{ i18n.ts._reversi.white }}:{{ engine.whiteCount }} {{ i18n.ts._reversi.total }}:{{ engine.blackCount + engine.whiteCount }}</div>
|
||||||
|
|
||||||
<div v-if="!game.isEnded && iAmPlayer" class="_buttonsCenter">
|
<div v-if="!game.isEnded && iAmPlayer" class="_buttonsCenter">
|
||||||
<MkButton danger @click="surrender">{{ i18n.ts._reversi.surrender }}</MkButton>
|
<MkButton danger @click="surrender">{{ i18n.ts._reversi.surrender }}</MkButton>
|
||||||
|
|
|
@ -141,7 +141,7 @@ async function unregisterKey(key) {
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
title: i18n.ts._2fa.removeKey,
|
title: i18n.ts._2fa.removeKey,
|
||||||
text: i18n.t('_2fa.removeKeyConfirm', { name: key.name }),
|
text: i18n.tsx._2fa.removeKeyConfirm({ name: key.name }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<details>
|
<details>
|
||||||
<summary>{{ i18n.ts.details }}</summary>
|
<summary>{{ i18n.ts.details }}</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
|
<li v-for="p in token.permission" :key="p">{{ i18n.ts._permissions[p] }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="!loading" class="_gaps">
|
<div v-if="!loading" class="_gaps">
|
||||||
<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
<MkInfo>{{ i18n.tsx._profile.avatarDecorationMax({ max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.tsx.remainingN({ n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
||||||
|
|
||||||
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
|
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
|
||||||
|
|
||||||
|
|
|
@ -77,9 +77,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkRadios v-model="mediaListWithOneImageAppearance">
|
<MkRadios v-model="mediaListWithOneImageAppearance">
|
||||||
<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
|
<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
|
||||||
<option value="expand">{{ i18n.ts.default }}</option>
|
<option value="expand">{{ i18n.ts.default }}</option>
|
||||||
<option value="16_9">{{ i18n.t('limitTo', { x: '16:9' }) }}</option>
|
<option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option>
|
||||||
<option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option>
|
<option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option>
|
||||||
<option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option>
|
<option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option>
|
||||||
</MkRadios>
|
</MkRadios>
|
||||||
</div>
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
|
@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkInput v-for="(_, i) in accountAliases" v-model="accountAliases[i]">
|
<MkInput v-for="(_, i) in accountAliases" v-model="accountAliases[i]">
|
||||||
<template #prefix><i class="ti ti-plane-arrival"></i></template>
|
<template #prefix><i class="ti ti-plane-arrival"></i></template>
|
||||||
<template #label>{{ i18n.t('_accountMigration.moveFromLabel', { n: i + 1 }) }}</template>
|
<template #label>{{ i18n.tsx._accountMigration.moveFromLabel({ n: i + 1 }) }}</template>
|
||||||
</MkInput>
|
</MkInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,7 +97,7 @@ async function move(): Promise<void> {
|
||||||
const account = moveToAccount.value;
|
const account = moveToAccount.value;
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('_accountMigration.migrationConfirm', { account }),
|
text: i18n.tsx._accountMigration.migrationConfirm({ account }),
|
||||||
});
|
});
|
||||||
if (confirm.canceled) return;
|
if (confirm.canceled) return;
|
||||||
await os.apiWithDialog('i/move', {
|
await os.apiWithDialog('i/move', {
|
||||||
|
|
|
@ -64,7 +64,7 @@ async function save() {
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: i18n.ts.regexpError,
|
title: i18n.ts.regexpError,
|
||||||
text: i18n.t('regexpErrorDescription', { tab: 'word mute', line: i + 1 }) + '\n' + err.toString(),
|
text: i18n.tsx.regexpErrorDescription({ tab: 'word mute', line: i + 1 }) + '\n' + err.toString(),
|
||||||
});
|
});
|
||||||
// re-throw error so these invalid settings are not saved
|
// re-throw error so these invalid settings are not saved
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.notificationRecieveConfig }}</template>
|
<template #label>{{ i18n.ts.notificationRecieveConfig }}</template>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFolder v-for="type in notificationTypes.filter(x => !nonConfigurableNotificationTypes.includes(x))" :key="type">
|
<MkFolder v-for="type in notificationTypes.filter(x => !nonConfigurableNotificationTypes.includes(x))" :key="type">
|
||||||
<template #label>{{ i18n.t('_notification._types.' + type) }}</template>
|
<template #label>{{ i18n.ts._notification._types[type] }}</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
{{
|
{{
|
||||||
$i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none :
|
$i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none :
|
||||||
|
|
|
@ -206,7 +206,7 @@ function changeAvatar(ev) {
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
text: i18n.t('cropImageAsk'),
|
text: i18n.ts.cropImageAsk,
|
||||||
okText: i18n.ts.cropYes,
|
okText: i18n.ts.cropYes,
|
||||||
cancelText: i18n.ts.cropNo,
|
cancelText: i18n.ts.cropNo,
|
||||||
});
|
});
|
||||||
|
@ -232,7 +232,7 @@ function changeBanner(ev) {
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
text: i18n.t('cropImageAsk'),
|
text: i18n.ts.cropImageAsk,
|
||||||
okText: i18n.ts.cropYes,
|
okText: i18n.ts.cropYes,
|
||||||
cancelText: i18n.ts.cropNo,
|
cancelText: i18n.ts.cropNo,
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.sounds }}</template>
|
<template #label>{{ i18n.ts.sounds }}</template>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkFolder v-for="type in operationTypes" :key="type">
|
<MkFolder v-for="type in operationTypes" :key="type">
|
||||||
<template #label>{{ i18n.t('_sfx.' + type) }}</template>
|
<template #label>{{ i18n.ts._sfx[type] }}</template>
|
||||||
<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
|
<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
|
||||||
|
|
||||||
<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
|
<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
|
||||||
|
@ -33,9 +33,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Ref, computed, ref } from 'vue';
|
import { Ref, computed, ref } from 'vue';
|
||||||
|
import XSound from './sounds.sound.vue';
|
||||||
import type { SoundType, OperationType } from '@/scripts/sound.js';
|
import type { SoundType, OperationType } from '@/scripts/sound.js';
|
||||||
import type { SoundStore } from '@/store.js';
|
import type { SoundStore } from '@/store.js';
|
||||||
import XSound from './sounds.sound.vue';
|
|
||||||
import MkRange from '@/components/MkRange.vue';
|
import MkRange from '@/components/MkRange.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
|
|
|
@ -33,7 +33,7 @@ async function install(code: string): Promise<void> {
|
||||||
await installTheme(code);
|
await installTheme(code);
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: i18n.t('_theme.installed', { name: theme.name }),
|
text: i18n.tsx._theme.installed({ name: theme.name }),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
switch (err.message.toLowerCase()) {
|
switch (err.message.toLowerCase()) {
|
||||||
|
|
|
@ -99,7 +99,7 @@ async function save(): Promise<void> {
|
||||||
async function del(): Promise<void> {
|
async function del(): Promise<void> {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('deleteAreYouSure', { x: webhook.name }),
|
text: i18n.tsx.deleteAreYouSure({ x: webhook.name }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i class="ti ti-user-check"></i>
|
<i class="ti ti-user-check"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="_gaps_m" style="padding: 32px;">
|
<div class="_gaps_m" style="padding: 32px;">
|
||||||
<div>{{ i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }) }}</div>
|
<div>{{ i18n.tsx.clickToFinishEmailVerification({ ok: i18n.ts.gotIt }) }}</div>
|
||||||
<div>
|
<div>
|
||||||
<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
|
<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
|
||||||
{{ submitting ? i18n.ts.processing : i18n.ts.gotIt }}<MkEllipsis v-if="submitting"/>
|
{{ submitting ? i18n.ts.processing : i18n.ts.gotIt }}<MkEllipsis v-if="submitting"/>
|
||||||
|
|
|
@ -208,7 +208,7 @@ async function saveAs() {
|
||||||
changed.value = false;
|
changed.value = false;
|
||||||
os.alert({
|
os.alert({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: i18n.t('_theme.installed', { name: theme.value.name }),
|
text: i18n.tsx._theme.installed({ name: theme.value.name }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</dl>
|
</dl>
|
||||||
<dl v-if="user.birthday" class="field">
|
<dl v-if="user.birthday" class="field">
|
||||||
<dt class="name"><i class="ti ti-cake ti-fw"></i> {{ i18n.ts.birthday }}</dt>
|
<dt class="name"><i class="ti ti-cake ti-fw"></i> {{ i18n.ts.birthday }}</dt>
|
||||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.t('yearsOld', { age }) }})</dd>
|
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.tsx.yearsOld({ age }) }})</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl class="field">
|
<dl class="field">
|
||||||
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
|
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
|
||||||
|
|
|
@ -66,7 +66,7 @@ function addApp() {
|
||||||
async function deleteFile(file: Misskey.entities.DriveFile) {
|
async function deleteFile(file: Misskey.entities.DriveFile) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('driveFileDeleteConfirm', { name: file.name }),
|
text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
|
@ -47,7 +47,7 @@ export async function getNoteClipMenu(props: {
|
||||||
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
|
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
|
text: i18n.tsx.confirmToUnclipAlreadyClippedNote({ name: clip.name }),
|
||||||
});
|
});
|
||||||
if (!confirm.canceled) {
|
if (!confirm.canceled) {
|
||||||
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
|
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
|
||||||
|
@ -231,7 +231,7 @@ export function getNoteMenu(props: {
|
||||||
|
|
||||||
function share(): void {
|
function share(): void {
|
||||||
navigator.share({
|
navigator.share({
|
||||||
title: i18n.t('noteOf', { user: appearNote.user.name }),
|
title: i18n.tsx.noteOf({ user: appearNote.user.name }),
|
||||||
text: appearNote.text,
|
text: appearNote.text,
|
||||||
url: `${url}/notes/${appearNote.id}`,
|
url: `${url}/notes/${appearNote.id}`,
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const getNoteSummary = (note: Misskey.entities.Note): string => {
|
||||||
|
|
||||||
// ファイルが添付されているとき
|
// ファイルが添付されているとき
|
||||||
if ((note.files || []).length !== 0) {
|
if ((note.files || []).length !== 0) {
|
||||||
summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`;
|
summary += ` (${i18n.tsx.withNFiles({ n: note.files.length })})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投票が添付されているとき
|
// 投票が添付されているとき
|
||||||
|
|
|
@ -14,37 +14,39 @@ type FlattenKeys<T extends ILocale, TPrediction> = keyof {
|
||||||
: never]: T[K];
|
: never]: T[K];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ParametersOf<T extends ILocale, TKey extends FlattenKeys<T, ParameterizedString<string>>> = T extends ILocale
|
type ParametersOf<T extends ILocale, TKey extends FlattenKeys<T, ParameterizedString>> = TKey extends `${infer K}.${infer C}`
|
||||||
? TKey extends `${infer K}.${infer C}`
|
// @ts-expect-error -- C は明らかに FlattenKeys<T[K], ParameterizedString> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
|
||||||
// @ts-expect-error -- C は明らかに FlattenKeys<T[K], ParameterizedString<string>> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
|
|
||||||
? ParametersOf<T[K], C>
|
? ParametersOf<T[K], C>
|
||||||
: TKey extends keyof T
|
: TKey extends keyof T
|
||||||
? T[TKey] extends ParameterizedString<infer P>
|
? T[TKey] extends ParameterizedString<infer P>
|
||||||
? P
|
? P
|
||||||
: never
|
: never
|
||||||
: never
|
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
type Ts<T extends ILocale> = {
|
type Tsx<T extends ILocale> = {
|
||||||
readonly [K in keyof T as T[K] extends ParameterizedString<string> ? never : K]: T[K] extends ILocale ? Ts<T[K]> : string;
|
readonly [K in keyof T as T[K] extends string ? never : K]: T[K] extends ParameterizedString<infer P>
|
||||||
|
? (arg: { readonly [_ in P]: string | number }) => string
|
||||||
|
// @ts-expect-error -- 証明省略
|
||||||
|
: Tsx<T[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class I18n<T extends ILocale> {
|
export class I18n<T extends ILocale> {
|
||||||
constructor(private locale: T) {
|
private tsxCache?: Tsx<T>;
|
||||||
|
|
||||||
|
constructor(public locale: T) {
|
||||||
//#region BIND
|
//#region BIND
|
||||||
this.t = this.t.bind(this);
|
this.t = this.t.bind(this);
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public get ts(): Ts<T> {
|
public get ts(): T {
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
class Handler<TTarget extends object> implements ProxyHandler<TTarget> {
|
class Handler<TTarget extends ILocale> implements ProxyHandler<TTarget> {
|
||||||
get(target: TTarget, p: string | symbol): unknown {
|
get(target: TTarget, p: string | symbol): unknown {
|
||||||
const value = target[p as keyof TTarget];
|
const value = target[p as keyof TTarget];
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 実際には null がくることはないので。
|
return new Proxy(value, new Handler<TTarget[keyof TTarget] & ILocale>());
|
||||||
return new Proxy(value!, new Handler<TTarget[keyof TTarget] & object>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
|
@ -63,19 +65,148 @@ export class I18n<T extends ILocale> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Proxy(this.locale, new Handler()) as Ts<T>;
|
return new Proxy(this.locale, new Handler());
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.locale as Ts<T>;
|
return this.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tsx(): Tsx<T> {
|
||||||
|
if (_DEV_) {
|
||||||
|
if (this.tsxCache) {
|
||||||
|
return this.tsxCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Handler<TTarget extends ILocale> implements ProxyHandler<TTarget> {
|
||||||
|
get(target: TTarget, p: string | symbol): unknown {
|
||||||
|
const value = target[p as keyof TTarget];
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return new Proxy(value, new Handler<TTarget[keyof TTarget] & ILocale>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const quasis: string[] = [];
|
||||||
|
const expressions: string[] = [];
|
||||||
|
let cursor = 0;
|
||||||
|
|
||||||
|
while (~cursor) {
|
||||||
|
const start = value.indexOf('{', cursor);
|
||||||
|
|
||||||
|
if (!~start) {
|
||||||
|
quasis.push(value.slice(cursor));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
quasis.push(value.slice(cursor, start));
|
||||||
|
|
||||||
|
const end = value.indexOf('}', start);
|
||||||
|
|
||||||
|
expressions.push(value.slice(start + 1, end));
|
||||||
|
|
||||||
|
cursor = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expressions.length) {
|
||||||
|
console.error(`Unexpected locale key: ${String(p)}`);
|
||||||
|
|
||||||
|
return () => value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (arg) => {
|
||||||
|
let str = quasis[0];
|
||||||
|
|
||||||
|
for (let i = 0; i < expressions.length; i++) {
|
||||||
|
if (!Object.hasOwn(arg, expressions[i])) {
|
||||||
|
console.error(`Missing locale parameters: ${expressions[i]} at ${String(p)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
str += arg[expressions[i]] + quasis[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`Unexpected locale key: ${String(p)}`);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tsxCache = new Proxy(this.locale, new Handler()) as unknown as Tsx<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (this.tsxCache) {
|
||||||
|
return this.tsxCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build(target: ILocale): Tsx<T> {
|
||||||
|
const result = {} as Tsx<T>;
|
||||||
|
|
||||||
|
for (const k in target) {
|
||||||
|
if (!Object.hasOwn(target, k)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = target[k as keyof typeof target];
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
result[k] = build(value as ILocale);
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
const quasis: string[] = [];
|
||||||
|
const expressions: string[] = [];
|
||||||
|
let cursor = 0;
|
||||||
|
|
||||||
|
while (~cursor) {
|
||||||
|
const start = value.indexOf('{', cursor);
|
||||||
|
|
||||||
|
if (!~start) {
|
||||||
|
quasis.push(value.slice(cursor));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
quasis.push(value.slice(cursor, start));
|
||||||
|
|
||||||
|
const end = value.indexOf('}', start);
|
||||||
|
|
||||||
|
expressions.push(value.slice(start + 1, end));
|
||||||
|
|
||||||
|
cursor = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expressions.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result[k] = (arg) => {
|
||||||
|
let str = quasis[0];
|
||||||
|
|
||||||
|
for (let i = 0; i < expressions.length; i++) {
|
||||||
|
str += arg[expressions[i]] + quasis[i + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tsxCache = build(this.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated なるべくこのメソッド使うよりも locale 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
|
* @deprecated なるべくこのメソッド使うよりも ts 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
|
||||||
*/
|
*/
|
||||||
public t<TKey extends FlattenKeys<T, string>>(key: TKey): string;
|
public t<TKey extends FlattenKeys<T, string>>(key: TKey): string;
|
||||||
public t<TKey extends FlattenKeys<T, ParameterizedString<string>>>(key: TKey, args: { readonly [_ in ParametersOf<T, TKey>]: string | number }): string;
|
/**
|
||||||
|
* @deprecated なるべくこのメソッド使うよりも tsx 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
|
||||||
|
*/
|
||||||
|
public t<TKey extends FlattenKeys<T, ParameterizedString>>(key: TKey, args: { readonly [_ in ParametersOf<T, TKey>]: string | number }): string;
|
||||||
public t(key: string, args?: { readonly [_: string]: string | number }) {
|
public t(key: string, args?: { readonly [_: string]: string | number }) {
|
||||||
let str: string | ParameterizedString<string> | ILocale = this.locale;
|
let str: string | ParameterizedString | ILocale = this.locale;
|
||||||
|
|
||||||
for (const k of key.split('.')) {
|
for (const k of key.split('.')) {
|
||||||
str = str[k];
|
str = str[k];
|
||||||
|
@ -113,3 +244,51 @@ export class I18n<T extends ILocale> {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (import.meta.vitest) {
|
||||||
|
const { describe, expect, it } = import.meta.vitest;
|
||||||
|
|
||||||
|
describe('i18n', () => {
|
||||||
|
it('t', () => {
|
||||||
|
const i18n = new I18n({
|
||||||
|
foo: 'foo',
|
||||||
|
bar: {
|
||||||
|
baz: 'baz',
|
||||||
|
qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
|
||||||
|
quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(i18n.t('foo')).toBe('foo');
|
||||||
|
expect(i18n.t('bar.baz')).toBe('baz');
|
||||||
|
expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
|
||||||
|
expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
|
||||||
|
});
|
||||||
|
it('ts', () => {
|
||||||
|
const i18n = new I18n({
|
||||||
|
foo: 'foo',
|
||||||
|
bar: {
|
||||||
|
baz: 'baz',
|
||||||
|
qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
|
||||||
|
quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(i18n.ts.foo).toBe('foo');
|
||||||
|
expect(i18n.ts.bar.baz).toBe('baz');
|
||||||
|
});
|
||||||
|
it('tsx', () => {
|
||||||
|
const i18n = new I18n({
|
||||||
|
foo: 'foo',
|
||||||
|
bar: {
|
||||||
|
baz: 'baz',
|
||||||
|
qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
|
||||||
|
quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
|
||||||
|
expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -189,7 +189,7 @@ const addColumn = async (ev) => {
|
||||||
const { canceled, result: column } = await os.select({
|
const { canceled, result: column } = await os.select({
|
||||||
title: i18n.ts._deck.addColumn,
|
title: i18n.ts._deck.addColumn,
|
||||||
items: columns.map(column => ({
|
items: columns.map(column => ({
|
||||||
value: column, text: i18n.t('_deck._columns.' + column),
|
value: column, text: i18n.ts._deck._columns[column],
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
@ -197,7 +197,7 @@ const addColumn = async (ev) => {
|
||||||
addColumnToStore({
|
addColumnToStore({
|
||||||
type: column,
|
type: column,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name: i18n.t('_deck._columns.' + column),
|
name: i18n.ts._deck._columns[column],
|
||||||
width: 330,
|
width: 330,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -256,7 +256,7 @@ function changeProfile(ev: MouseEvent) {
|
||||||
async function deleteProfile() {
|
async function deleteProfile() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.t('deleteAreYouSure', { x: deckStore.state.profile }),
|
text: i18n.tsx.deleteAreYouSure({ x: deckStore.state.profile }),
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="[$style.root, { _panel: !widgetProps.transparent }]" data-cy-mkw-calendar>
|
<div :class="[$style.root, { _panel: !widgetProps.transparent }]" data-cy-mkw-calendar>
|
||||||
<div :class="[$style.calendar, { [$style.isHoliday]: isHoliday }]">
|
<div :class="[$style.calendar, { [$style.isHoliday]: isHoliday }]">
|
||||||
<p :class="$style.monthAndYear">
|
<p :class="$style.monthAndYear">
|
||||||
<span :class="$style.year">{{ i18n.t('yearX', { year }) }}</span>
|
<span :class="$style.year">{{ i18n.tsx.yearX({ year }) }}</span>
|
||||||
<span :class="$style.month">{{ i18n.t('monthX', { month }) }}</span>
|
<span :class="$style.month">{{ i18n.tsx.monthX({ month }) }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p v-if="month === 1 && day === 1" class="day">🎉{{ i18n.t('dayX', { day }) }}<span style="display: inline-block; transform: scaleX(-1);">🎉</span></p>
|
<p v-if="month === 1 && day === 1" class="day">🎉{{ i18n.tsx.dayX({ day }) }}<span style="display: inline-block; transform: scaleX(-1);">🎉</span></p>
|
||||||
<p v-else :class="$style.day">{{ i18n.t('dayX', { day }) }}</p>
|
<p v-else :class="$style.day">{{ i18n.tsx.dayX({ day }) }}</p>
|
||||||
<p :class="$style.weekDay">{{ weekDay }}</p>
|
<p :class="$style.weekDay">{{ weekDay }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.info">
|
<div :class="$style.info">
|
||||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<p v-if="widgetProps.folderId == null">
|
<p v-if="widgetProps.folderId == null">
|
||||||
{{ i18n.ts.folder }}
|
{{ i18n.ts.folder }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ i18n.t('no-image') }}</p>
|
<p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ i18n.ts['no-image'] }}</p>
|
||||||
<div ref="slideA" class="slide a"></div>
|
<div ref="slideA" class="slide a"></div>
|
||||||
<div ref="slideB" class="slide b"></div>
|
<div ref="slideB" class="slide b"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
<template #header>
|
<template #header>
|
||||||
<button class="_button" @click="choose">
|
<button class="_button" @click="choose">
|
||||||
<span>{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : i18n.t('_timelines.' + widgetProps.src) }}</span>
|
<span>{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : i18n.ts._timelines[widgetProps.src] }}</span>
|
||||||
<i :class="menuOpened ? 'ti ti-chevron-up' : 'ti ti-chevron-down'" style="margin-left: 8px;"></i>
|
<i :class="menuOpened ? 'ti ti-chevron-up' : 'ti ti-chevron-down'" style="margin-left: 8px;"></i>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-for="stat in stats" :key="stat.tag">
|
<div v-for="stat in stats" :key="stat.tag">
|
||||||
<div class="tag">
|
<div class="tag">
|
||||||
<MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA>
|
<MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA>
|
||||||
<p>{{ i18n.t('nUsersMentioned', { n: stat.usersCount }) }}</p>
|
<p>{{ i18n.tsx.nUsersMentioned({ n: stat.usersCount }) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<MkMiniChart class="chart" :src="stat.chart"/>
|
<MkMiniChart class="chart" :src="stat.chart"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
"vite/client",
|
"vite/client",
|
||||||
|
"vitest/importMeta",
|
||||||
],
|
],
|
||||||
"lib": [
|
"lib": [
|
||||||
"esnext",
|
"esnext",
|
||||||
|
|
|
@ -155,6 +155,7 @@ export function getConfig(): UserConfig {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
includeSource: ['src/**/*.ts'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue