summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorAcid Chicken (硫酸鶏) <root@acid-chicken.com>2024-01-20 08:11:59 +0900
committerGitHub <noreply@github.com>2024-01-20 08:11:59 +0900
commit7881f06be060dd955499a6beff3ad1967ed628f6 (patch)
tree7b91a1c5e0f1ef99d9f66e702c7d954a54958a56 /packages/frontend/src
parentfeat: reversi (diff)
downloadmisskey-7881f06be060dd955499a6beff3ad1967ed628f6.tar.gz
misskey-7881f06be060dd955499a6beff3ad1967ed628f6.tar.bz2
misskey-7881f06be060dd955499a6beff3ad1967ed628f6.zip
refactor: deprecate i18n.t (#13039)
* refactor: deprecate i18n.t * revert: deprecate i18n.t This reverts commit 7dbf873a2f745040ee723df5db659acacff84e12. * chore: reimpl
Diffstat (limited to 'packages/frontend/src')
-rw-r--r--packages/frontend/src/boot/main-boot.ts2
-rw-r--r--packages/frontend/src/components/MkAnnouncementDialog.vue2
-rw-r--r--packages/frontend/src/components/MkCwButton.vue4
-rw-r--r--packages/frontend/src/components/MkDateSeparatedList.vue2
-rw-r--r--packages/frontend/src/components/MkDialog.vue4
-rw-r--r--packages/frontend/src/components/MkDrive.vue4
-rw-r--r--packages/frontend/src/components/MkFollowButton.vue2
-rw-r--r--packages/frontend/src/components/MkNote.vue2
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue2
-rw-r--r--packages/frontend/src/components/MkNotification.vue4
-rw-r--r--packages/frontend/src/components/MkNotificationSelectWindow.vue2
-rw-r--r--packages/frontend/src/components/MkPoll.vue15
-rw-r--r--packages/frontend/src/components/MkPollEditor.vue2
-rw-r--r--packages/frontend/src/components/MkSignupDialog.form.vue2
-rw-r--r--packages/frontend/src/components/MkSignupDialog.rules.vue6
-rw-r--r--packages/frontend/src/components/MkSubNoteContent.vue2
-rw-r--r--packages/frontend/src/components/MkTokenGenerateWindow.vue4
-rw-r--r--packages/frontend/src/components/MkTutorialDialog.vue2
-rw-r--r--packages/frontend/src/components/MkUserAnnouncementEditDialog.vue2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.Profile.vue2
-rw-r--r--packages/frontend/src/components/MkUserSetupDialog.vue4
-rw-r--r--packages/frontend/src/components/MkWidgets.vue4
-rw-r--r--packages/frontend/src/components/global/I18n.vue46
-rw-r--r--packages/frontend/src/components/global/MkTime.stories.impl.ts10
-rw-r--r--packages/frontend/src/components/global/MkTime.vue28
-rw-r--r--packages/frontend/src/components/global/i18n.ts29
-rw-r--r--packages/frontend/src/components/index.ts2
-rw-r--r--packages/frontend/src/pages/about.vue2
-rw-r--r--packages/frontend/src/pages/admin-file.vue2
-rw-r--r--packages/frontend/src/pages/admin-user.vue8
-rw-r--r--packages/frontend/src/pages/admin/ads.vue2
-rw-r--r--packages/frontend/src/pages/admin/announcements.vue4
-rw-r--r--packages/frontend/src/pages/admin/branding.vue8
-rw-r--r--packages/frontend/src/pages/admin/relays.vue2
-rw-r--r--packages/frontend/src/pages/admin/roles.role.vue2
-rw-r--r--packages/frontend/src/pages/announcements.vue2
-rw-r--r--packages/frontend/src/pages/auth.form.vue6
-rw-r--r--packages/frontend/src/pages/auth.vue2
-rw-r--r--packages/frontend/src/pages/avatar-decorations.vue2
-rw-r--r--packages/frontend/src/pages/channel-editor.vue2
-rw-r--r--packages/frontend/src/pages/clip.vue2
-rw-r--r--packages/frontend/src/pages/drive.file.info.vue2
-rw-r--r--packages/frontend/src/pages/drop-and-fusion.vue2
-rw-r--r--packages/frontend/src/pages/emoji-edit-dialog.vue2
-rw-r--r--packages/frontend/src/pages/flash/flash-edit.vue2
-rw-r--r--packages/frontend/src/pages/follow.vue2
-rw-r--r--packages/frontend/src/pages/instance-info.vue4
-rw-r--r--packages/frontend/src/pages/invite.vue4
-rw-r--r--packages/frontend/src/pages/miauth.vue6
-rw-r--r--packages/frontend/src/pages/my-antennas/editor.vue2
-rw-r--r--packages/frontend/src/pages/my-lists/index.vue2
-rw-r--r--packages/frontend/src/pages/my-lists/list.vue4
-rw-r--r--packages/frontend/src/pages/note.vue2
-rw-r--r--packages/frontend/src/pages/notifications.vue2
-rw-r--r--packages/frontend/src/pages/oauth.vue6
-rw-r--r--packages/frontend/src/pages/page-editor/page-editor.vue2
-rw-r--r--packages/frontend/src/pages/reversi/game.board.vue8
-rw-r--r--packages/frontend/src/pages/settings/2fa.vue2
-rw-r--r--packages/frontend/src/pages/settings/apps.vue2
-rw-r--r--packages/frontend/src/pages/settings/avatar-decoration.vue2
-rw-r--r--packages/frontend/src/pages/settings/general.vue6
-rw-r--r--packages/frontend/src/pages/settings/migration.vue4
-rw-r--r--packages/frontend/src/pages/settings/mute-block.word-mute.vue2
-rw-r--r--packages/frontend/src/pages/settings/notifications.vue2
-rw-r--r--packages/frontend/src/pages/settings/profile.vue4
-rw-r--r--packages/frontend/src/pages/settings/sounds.vue4
-rw-r--r--packages/frontend/src/pages/settings/theme.install.vue2
-rw-r--r--packages/frontend/src/pages/settings/webhook.edit.vue2
-rw-r--r--packages/frontend/src/pages/signup-complete.vue2
-rw-r--r--packages/frontend/src/pages/theme-editor.vue2
-rw-r--r--packages/frontend/src/pages/user/home.vue2
-rw-r--r--packages/frontend/src/scripts/get-drive-file-menu.ts2
-rw-r--r--packages/frontend/src/scripts/get-note-menu.ts4
-rw-r--r--packages/frontend/src/scripts/get-note-summary.ts2
-rw-r--r--packages/frontend/src/scripts/i18n.ts221
-rw-r--r--packages/frontend/src/ui/deck.vue6
-rw-r--r--packages/frontend/src/widgets/WidgetCalendar.vue8
-rw-r--r--packages/frontend/src/widgets/WidgetSlideshow.vue2
-rw-r--r--packages/frontend/src/widgets/WidgetTimeline.vue2
-rw-r--r--packages/frontend/src/widgets/WidgetTrends.vue2
80 files changed, 386 insertions, 189 deletions
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index bdb145b39a..c99118f9b2 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -205,7 +205,7 @@ export async function mainBoot() {
const lastUsedDate = parseInt(lastUsed, 10);
// 二時間以上前なら
if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
- toast(i18n.t('welcomeBackWithName', {
+ toast(i18n.tsx.welcomeBackWithName({
name: $i.name || $i.username,
}));
}
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue
index c649e69cd0..54cbbe18c2 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.vue
+++ b/packages/frontend/src/components/MkAnnouncementDialog.vue
@@ -44,7 +44,7 @@ async function ok() {
const confirm = await os.confirm({
type: 'question',
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;
}
diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue
index 4a6d2dfba2..ca19a2122d 100644
--- a/packages/frontend/src/components/MkCwButton.vue
+++ b/packages/frontend/src/components/MkCwButton.vue
@@ -41,9 +41,9 @@ const emit = defineEmits<{
const label = computed(() => {
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.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] : [],
] as string[][]).join(' / ');
});
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index 0a71b689fe..7b9a052868 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -46,7 +46,7 @@ export default defineComponent({
function getDateText(time: string) {
const date = new Date(time).getDate();
const month = new Date(time).getMonth() + 1;
- return i18n.t('monthAndDay', {
+ return i18n.tsx.monthAndDay({
month: month.toString(),
day: date.toString(),
});
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 3c1f83d335..3fc9f0e357 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -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">
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
<template #caption>
- <span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.t('_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-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.tsx._dialog.charactersBelow({ current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
</template>
</MkInput>
<MkSelect v-if="select" v-model="selectedValue" autofocus>
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index dbf98cd622..560d5502d4 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -82,8 +82,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.ts.loadMore }}</MkButton>
</div>
<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 && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</div>
+ <div v-if="draghover">{{ i18n.ts['empty-draghover'] }}</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>
</div>
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index 78c4fb3cd2..6bc96be1b9 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -84,7 +84,7 @@ async function onClick() {
if (isFollowing.value) {
const { canceled } = await os.confirm({
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;
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 9c4354ef5f..d7bb64661b 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<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"/>
</div>
</div>
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index e941827d74..1f5b283cfe 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="translating || translation" :class="$style.translation">
<MkLoading v-if="translating" mini/>
<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"/>
</div>
</div>
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index ce8b054b39..92be20c6f6 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -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 === '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>
- <span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.t('_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 === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.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>
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
</header>
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index 6725776f43..0e77d2a6aa 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
</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>
</MkSpacer>
</MkModalWindow>
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index 4cac1fe9c3..7c58b58697 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -11,12 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<span :class="$style.fg">
<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"/>
- <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>
</li>
</ul>
<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>
<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>
@@ -47,10 +47,11 @@ const remaining = ref(-1);
const total = computed(() => sum(props.note.poll.choices.map(x => x.votes)));
const closed = computed(() => remaining.value === 0);
const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted));
-const timer = computed(() => i18n.t(
- remaining.value >= 86400 ? '_poll.remainingDays' :
- remaining.value >= 3600 ? '_poll.remainingHours' :
- remaining.value >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', {
+const timer = computed(() => i18n.tsx._poll[
+ remaining.value >= 86400 ? 'remainingDays' :
+ remaining.value >= 3600 ? 'remainingHours' :
+ remaining.value >= 60 ? 'remainingMinutes' : 'remainingSeconds'
+ ]({
s: Math.floor(remaining.value % 60),
m: Math.floor(remaining.value / 60) % 60,
h: Math.floor(remaining.value / 3600) % 24,
@@ -81,7 +82,7 @@ const vote = async (id) => {
const { canceled } = await os.confirm({
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;
diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue
index 43e576d1ab..34f6c72b6e 100644
--- a/packages/frontend/src/components/MkPollEditor.vue
+++ b/packages/frontend/src/components/MkPollEditor.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</p>
<ul>
<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>
<button class="_button" @click="remove(i)">
<i class="ti ti-x"></i>
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 79e17c9aef..e27510fb34 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -263,7 +263,7 @@ async function onSubmit(): Promise<void> {
os.alert({
type: 'success',
title: i18n.ts._signup.almostThere,
- text: i18n.t('_signup.emailSent', { email: email.value }),
+ text: i18n.tsx._signup.emailSent({ email: email.value }),
});
emit('signupEmailPending');
} else {
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
index 8cf7ce92ad..d42b496a34 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.vue
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -105,7 +105,7 @@ async function updateAgreeServerRules(v: boolean) {
const confirm = await os.confirm({
type: 'question',
title: i18n.ts.doYouAgree,
- text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.serverRules }),
+ text: i18n.tsx.iHaveReadXCarefullyAndAgree({ x: i18n.ts.serverRules }),
});
if (confirm.canceled) return;
agreeServerRules.value = true;
@@ -119,7 +119,7 @@ async function updateAgreeTosAndPrivacyPolicy(v: boolean) {
const confirm = await os.confirm({
type: 'question',
title: i18n.ts.doYouAgree,
- text: i18n.t('iHaveReadXCarefullyAndAgree', {
+ text: i18n.tsx.iHaveReadXCarefullyAndAgree({
x: tosPrivacyPolicyLabel.value,
}),
});
@@ -135,7 +135,7 @@ async function updateAgreeNote(v: boolean) {
const confirm = await os.confirm({
type: 'question',
title: i18n.ts.doYouAgree,
- text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.basicNotesBeforeCreateAccount }),
+ text: i18n.tsx.iHaveReadXCarefullyAndAgree({ x: i18n.ts.basicNotesBeforeCreateAccount }),
});
if (confirm.canceled) return;
agreeNote.value = true;
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 438140649e..13d0e6c2ca 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -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>
</div>
<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"/>
</details>
<details v-if="note.poll">
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index a42767e1b6..28f2f29b7d 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -33,12 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
</div>
<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 v-if="iAmAdmin" :class="$style.adminPermissions">
<div :class="$style.adminPermissionsHeader"><b>{{ i18n.ts.adminPermission }}</b></div>
<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>
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue
index 963e78a1ff..9c3b46e133 100644
--- a/packages/frontend/src/components/MkTutorialDialog.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.vue
@@ -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>
</template>
</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;">
<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>
diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
index af094a8e8c..9e3a78fe22 100644
--- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
+++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
@@ -118,7 +118,7 @@ async function done() {
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('removeAreYouSure', { x: title.value }),
+ text: i18n.tsx.removeAreYouSure({ x: title.value }),
});
if (canceled) return;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
index 37aa677b44..f082833838 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
@@ -68,7 +68,7 @@ function setAvatar(ev) {
const { canceled } = await os.confirm({
type: 'question',
- text: i18n.t('cropImageAsk'),
+ text: i18n.ts.cropImageAsk,
okText: i18n.ts.cropYes,
cancelText: i18n.ts.cropNo,
});
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index 05b55f77a7..4b0c540829 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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>
<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;"/>
<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>
@@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps" style="text-align: center;">
<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>{{ 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;">
<MkButton rounded primary gradate data-cy-user-setup-continue @click="launchTutorial()">{{ i18n.ts._initialAccountSetting.startTutorial }} <i class="ti ti-arrow-right"></i></MkButton>
</div>
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index ee4e29dd8f..b8b253de06 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<header :class="$style.editHeader">
<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
<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>
<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>
@@ -109,7 +109,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
os.contextMenu([{
type: 'label',
- text: i18n.t(`_widgets.${widget.name}`),
+ text: i18n.ts._widgets[widget.name],
}, {
icon: 'ti ti-settings',
text: i18n.ts.settings,
diff --git a/packages/frontend/src/components/global/I18n.vue b/packages/frontend/src/components/global/I18n.vue
new file mode 100644
index 0000000000..162aa2bcf8
--- /dev/null
+++ b/packages/frontend/src/components/global/I18n.vue
@@ -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>
diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts
index 0eeefa4859..0e7f6a9bdf 100644
--- a/packages/frontend/src/components/global/MkTime.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts
@@ -123,7 +123,7 @@ export const DetailNow = {
export const RelativeOneHourAgo = {
...Empty,
async play({ canvasElement }) {
- await expect(canvasElement).toHaveTextContent(i18n.t('_ago.hoursAgo', { n: 1 }));
+ await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.hoursAgo({ n: 1 }));
},
args: {
...Empty.args,
@@ -162,7 +162,7 @@ export const DetailOneHourAgo = {
export const RelativeOneDayAgo = {
...Empty,
async play({ canvasElement }) {
- await expect(canvasElement).toHaveTextContent(i18n.t('_ago.daysAgo', { n: 1 }));
+ await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.daysAgo({ n: 1 }));
},
args: {
...Empty.args,
@@ -201,7 +201,7 @@ export const DetailOneDayAgo = {
export const RelativeOneWeekAgo = {
...Empty,
async play({ canvasElement }) {
- await expect(canvasElement).toHaveTextContent(i18n.t('_ago.weeksAgo', { n: 1 }));
+ await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.weeksAgo({ n: 1 }));
},
args: {
...Empty.args,
@@ -240,7 +240,7 @@ export const DetailOneWeekAgo = {
export const RelativeOneMonthAgo = {
...Empty,
async play({ canvasElement }) {
- await expect(canvasElement).toHaveTextContent(i18n.t('_ago.monthsAgo', { n: 1 }));
+ await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.monthsAgo({ n: 1 }));
},
args: {
...Empty.args,
@@ -279,7 +279,7 @@ export const DetailOneMonthAgo = {
export const RelativeOneYearAgo = {
...Empty,
async play({ canvasElement }) {
- await expect(canvasElement).toHaveTextContent(i18n.t('_ago.yearsAgo', { n: 1 }));
+ await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.yearsAgo({ n: 1 }));
},
args: {
...Empty.args,
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index e11db9dc31..2b0bf246ad 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -55,21 +55,21 @@ const relative = computed<string>(() => {
if (invalid) return i18n.ts._ago.invalid;
return (
- ago.value >= 31536000 ? i18n.t('_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 >= 604800 ? i18n.t('_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 >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago.value / 3600).toString() }) :
- ago.value >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago.value / 60)).toString() }) :
- ago.value >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago.value % 60)).toString() }) :
+ ago.value >= 31536000 ? i18n.tsx._ago.yearsAgo({ n: Math.round(ago.value / 31536000).toString() }) :
+ ago.value >= 2592000 ? i18n.tsx._ago.monthsAgo({ n: Math.round(ago.value / 2592000).toString() }) :
+ ago.value >= 604800 ? i18n.tsx._ago.weeksAgo({ n: Math.round(ago.value / 604800).toString() }) :
+ ago.value >= 86400 ? i18n.tsx._ago.daysAgo({ n: Math.round(ago.value / 86400).toString() }) :
+ ago.value >= 3600 ? i18n.tsx._ago.hoursAgo({ n: Math.round(ago.value / 3600).toString() }) :
+ ago.value >= 60 ? i18n.tsx._ago.minutesAgo({ 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 < -31536000 ? i18n.t('_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 < -604800 ? i18n.t('_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 < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago.value / 3600).toString() }) :
- ago.value < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago.value / 60)).toString() }) :
- i18n.t('_timeIn.seconds', { n: (~~(-ago.value % 60)).toString() })
+ ago.value < -31536000 ? i18n.tsx._timeIn.years({ n: Math.round(-ago.value / 31536000).toString() }) :
+ ago.value < -2592000 ? i18n.tsx._timeIn.months({ n: Math.round(-ago.value / 2592000).toString() }) :
+ ago.value < -604800 ? i18n.tsx._timeIn.weeks({ n: Math.round(-ago.value / 604800).toString() }) :
+ ago.value < -86400 ? i18n.tsx._timeIn.days({ n: Math.round(-ago.value / 86400).toString() }) :
+ ago.value < -3600 ? i18n.tsx._timeIn.hours({ n: Math.round(-ago.value / 3600).toString() }) :
+ ago.value < -60 ? i18n.tsx._timeIn.minutes({ n: (~~(-ago.value / 60)).toString() }) :
+ i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() })
);
});
diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts
deleted file mode 100644
index 2f4d7edabd..0000000000
--- a/packages/frontend/src/components/global/i18n.ts
+++ /dev/null
@@ -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]()));
-}
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index a3e13c3a50..f3b476b15c 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -16,7 +16,7 @@ import MkUserName from './global/MkUserName.vue';
import MkEllipsis from './global/MkEllipsis.vue';
import MkTime from './global/MkTime.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 MkLoading from './global/MkLoading.vue';
import MkError from './global/MkError.vue';
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index 4ba1b6da76..69cb6ef647 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #key>Misskey</template>
<template #value>{{ version }}</template>
</MkKeyValue>
- <div v-html="i18n.t('poweredByMisskeyDescription', { name: instance.name ?? host })">
+ <div v-html="i18n.tsx.poweredByMisskeyDescription({ name: instance.name ?? host })">
</div>
<FormLink to="/about-misskey">{{ i18n.ts.aboutMisskey }}</FormLink>
</div>
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index 4a9c659a97..84f3d1f3f1 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -104,7 +104,7 @@ fetch();
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('removeAreYouSure', { x: file.value.name }),
+ text: i18n.tsx.removeAreYouSure({ x: file.value.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index 85417f0ecb..530bcca04a 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -182,9 +182,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSelect>
</div>
<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>
- <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>
</div>
</div>
@@ -307,7 +307,7 @@ async function resetPassword() {
});
os.alert({
type: 'success',
- text: i18n.t('newPasswordIs', { password }),
+ text: i18n.tsx.newPasswordIs({ password }),
});
}
}
@@ -390,7 +390,7 @@ async function deleteAccount() {
if (confirm.canceled) return;
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;
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index eb9aef0e48..fe55fe3a02 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -160,7 +160,7 @@ function add() {
function remove(ad) {
os.confirm({
type: 'warning',
- text: i18n.t('removeAreYouSure', { x: ad.url }),
+ text: i18n.tsx.removeAreYouSure({ x: ad.url }),
}).then(({ canceled }) => {
if (canceled) return;
ads.value = ads.value.filter(x => x !== ad);
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index f941d512b3..44552fb88c 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
{{ i18n.ts._announcement.needConfirmationToRead }}
</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">
<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>
@@ -109,7 +109,7 @@ function add() {
function del(announcement) {
os.confirm({
type: 'warning',
- text: i18n.t('deleteAreYouSure', { x: announcement.title }),
+ text: i18n.tsx.deleteAreYouSure({ x: announcement.title }),
}).then(({ canceled }) => {
if (canceled) return;
announcements.value = announcements.value.filter(x => x !== announcement);
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 72b47949e7..dbbb3941d8 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #prefix><i class="ti ti-link"></i></template>
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</template>
<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.appIconStyleRecommendation }}</div>
- <div><strong>{{ i18n.t('_serverSettings.appIconResolutionMustBe', { resolution: '192x192px' }) }}</strong></div>
+ <div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '192x192px' }) }}</strong></div>
</template>
</MkInput>
@@ -30,10 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #prefix><i class="ti ti-link"></i></template>
<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</template>
<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.appIconStyleRecommendation }}</div>
- <div><strong>{{ i18n.t('_serverSettings.appIconResolutionMustBe', { resolution: '512x512px' }) }}</strong></div>
+ <div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '512x512px' }) }}</strong></div>
</template>
</MkInput>
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index 6811a8eba5..847c8bc1d4 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -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-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>
- <span>{{ i18n.t(`_relayStatus.${relay.status}`) }}</span>
+ <span>{{ i18n.ts._relayStatus[relay.status] }}</span>
</div>
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
</div>
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index ff29f4ec1f..ad58255576 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -104,7 +104,7 @@ function edit() {
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('deleteAreYouSure', { x: role.name }),
+ text: i18n.tsx.deleteAreYouSure({ x: role.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index c31c6d0903..e3c0ea574a 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -78,7 +78,7 @@ async function read(announcement) {
const confirm = await os.confirm({
type: 'question',
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;
}
diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue
index 39a7924f94..50fd696af3 100644
--- a/packages/frontend/src/pages/auth.form.vue
+++ b/packages/frontend/src/pages/auth.form.vue
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<section>
<div v-if="app.permission.length > 0">
- <p>{{ i18n.t('_auth.permission', { name }) }}</p>
+ <p>{{ i18n.tsx._auth.permission({ name }) }}</p>
<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>
</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">
<MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
<MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index fd38e22ce8..9b409aa4f8 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<h1>{{ i18n.ts._auth.denied }}</h1>
</div>
<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">
{{ i18n.ts._auth.callback }}
<MkEllipsis/>
diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue
index 376679fd17..f71a7685bd 100644
--- a/packages/frontend/src/pages/avatar-decorations.vue
+++ b/packages/frontend/src/pages/avatar-decorations.vue
@@ -60,7 +60,7 @@ function add() {
function del(avatarDecoration) {
os.confirm({
type: 'warning',
- text: i18n.t('deleteAreYouSure', { x: avatarDecoration.name }),
+ text: i18n.tsx.deleteAreYouSure({ x: avatarDecoration.name }),
}).then(({ canceled }) => {
if (canceled) return;
avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 99b93444db..bbe5dc0a4e 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -174,7 +174,7 @@ function save() {
async function archive() {
const { canceled } = await os.confirm({
type: 'warning',
- title: i18n.t('channelArchiveConfirmTitle', { name: name.value }),
+ title: i18n.tsx.channelArchiveConfirmTitle({ name: name.value }),
text: i18n.ts.channelArchiveConfirmDescription,
});
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index e55e99a6fa..8640561583 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -145,7 +145,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
handler: async (): Promise<void> => {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('deleteAreYouSure', { x: clip.value.name }),
+ text: i18n.tsx.deleteAreYouSure({ x: clip.value.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue
index 64c3ad70ba..3e45f5e88c 100644
--- a/packages/frontend/src/pages/drive.file.info.vue
+++ b/packages/frontend/src/pages/drive.file.info.vue
@@ -180,7 +180,7 @@ async function deleteFile() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('driveFileDeleteConfirm', { name: file.value.name }),
+ text: i18n.tsx.driveFileDeleteConfirm({ name: file.value.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
index beb2e714e0..b995521dfb 100644
--- a/packages/frontend/src/pages/drop-and-fusion.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.frame">
<div :class="$style.frameInner">
<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-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
<MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue
index 1ef150bc2d..9dcc7bc035 100644
--- a/packages/frontend/src/pages/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/emoji-edit-dialog.vue
@@ -185,7 +185,7 @@ async function done() {
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('removeAreYouSure', { x: name.value }),
+ text: i18n.tsx.removeAreYouSure({ x: name.value }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index ba350f1c0a..8f60b83a6c 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -438,7 +438,7 @@ function show() {
async function del() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('deleteAreYouSure', { x: flash.value.title }),
+ text: i18n.tsx.deleteAreYouSure({ x: flash.value.title }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/follow.vue b/packages/frontend/src/pages/follow.vue
index eefef828bd..44364bb0f2 100644
--- a/packages/frontend/src/pages/follow.vue
+++ b/packages/frontend/src/pages/follow.vue
@@ -20,7 +20,7 @@ import { mainRouter } from '@/global/router/main.js';
async function follow(user): Promise<void> {
const { canceled } = await os.confirm({
type: 'question',
- text: i18n.t('followConfirm', { name: user.name || user.username }),
+ text: i18n.tsx.followConfirm({ name: user.name || user.username }),
});
if (canceled) {
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 4211dc0d87..9519a8a3f4 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -95,9 +95,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSelect>
</div>
<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>
- <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>
</div>
</div>
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
index 61030741fa..d8613a67d3 100644
--- a/packages/frontend/src/pages/invite.vue
+++ b/packages/frontend/src/pages/invite.vue
@@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</MKSpacer>
<MkSpacer v-else :contentMax="800">
<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>
- <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">
<template #default="{ items }">
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index 539cb462ad..27ae1bdbc8 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -20,13 +20,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-else>
<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>
<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>
</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 :class="$style.buttons">
<MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton>
diff --git a/packages/frontend/src/pages/my-antennas/editor.vue b/packages/frontend/src/pages/my-antennas/editor.vue
index 45acbb2158..a4c6ca6f52 100644
--- a/packages/frontend/src/pages/my-antennas/editor.vue
+++ b/packages/frontend/src/pages/my-antennas/editor.vue
@@ -116,7 +116,7 @@ async function saveAntenna() {
async function deleteAntenna() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('removeAreYouSure', { x: props.antenna.name }),
+ text: i18n.tsx.removeAreYouSure({ x: props.antenna.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 14e2315843..295112b0ba 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<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 }`">
- <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"/>
</MkA>
</div>
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index 85775a2fdd..7207e956db 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFolder defaultOpen>
<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">
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
@@ -155,7 +155,7 @@ async function deleteList() {
if (!list.value) return;
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('removeAreYouSure', { x: list.value.name }),
+ text: i18n.tsx.removeAreYouSure({ x: list.value.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 4c0e9bbb98..9b72f9b2ac 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -147,7 +147,7 @@ definePageMetadata(computed(() => note.value ? {
avatar: note.value.user,
path: `/notes/${note.value.id}`,
share: {
- title: i18n.t('noteOf', { user: note.value.user.name }),
+ title: i18n.tsx.noteOf({ user: note.value.user.name }),
text: note.value.text,
},
} : null));
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index 8913a89adb..e6098c90b3 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -51,7 +51,7 @@ const directNotesPagination = {
function setFilter(ev) {
const typeItems = notificationTypes.map(t => ({
- text: i18n.t(`_notification._types.${t}`),
+ text: i18n.ts._notification._types[t],
active: includeTypes.value && includeTypes.value.includes(t),
action: () => {
includeTypes.value = [t];
diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue
index 878fa6be4e..38b9dd60c4 100644
--- a/packages/frontend/src/pages/oauth.vue
+++ b/packages/frontend/src/pages/oauth.vue
@@ -9,13 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="800">
<div v-if="$i">
<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>
<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>
</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>
<form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post">
<input name="login_token" type="hidden" :value="$i.token"/>
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index 6db72dccba..bd85b97d59 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -175,7 +175,7 @@ function save() {
function del() {
os.confirm({
type: 'warning',
- text: i18n.t('removeAreYouSure', { x: title.value.trim() }),
+ text: i18n.tsx.removeAreYouSure({ x: title.value.trim() }),
}).then(({ canceled }) => {
if (canceled) return;
misskeyApi('pages/delete', {
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
index 18fd74427c..582967ad2b 100644
--- a/packages/frontend/src/pages/reversi/game.board.vue
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -10,17 +10,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<div style="overflow: clip; line-height: 28px;">
<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/>
</div>
<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 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="game.isEnded && logPos == logs.length" class="result">
<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>
</template>
<template v-else>{{ i18n.ts._reversi.drawn }}</template>
@@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</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">
<MkButton danger @click="surrender">{{ i18n.ts._reversi.surrender }}</MkButton>
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue
index 35331738fd..dae1f03ccb 100644
--- a/packages/frontend/src/pages/settings/2fa.vue
+++ b/packages/frontend/src/pages/settings/2fa.vue
@@ -141,7 +141,7 @@ async function unregisterKey(key) {
const confirm = await os.confirm({
type: 'question',
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;
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index 4a778d4b38..525b4e7519 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<details>
<summary>{{ i18n.ts.details }}</summary>
<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>
</details>
<div>
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue
index 70565cc990..ecfa1ca257 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<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/>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index e52a5ee04f..bbc910a32a 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -77,9 +77,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkRadios v-model="mediaListWithOneImageAppearance">
<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
<option value="expand">{{ i18n.ts.default }}</option>
- <option value="16_9">{{ i18n.t('limitTo', { x: '16:9' }) }}</option>
- <option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option>
- <option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option>
+ <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option>
+ <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option>
+ <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option>
</MkRadios>
</div>
</FormSection>
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index 2699f0ad63..6e5de0a333 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps">
<MkInput v-for="(_, i) in accountAliases" v-model="accountAliases[i]">
<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>
</div>
</div>
@@ -97,7 +97,7 @@ async function move(): Promise<void> {
const account = moveToAccount.value;
const confirm = await os.confirm({
type: 'warning',
- text: i18n.t('_accountMigration.migrationConfirm', { account }),
+ text: i18n.tsx._accountMigration.migrationConfirm({ account }),
});
if (confirm.canceled) return;
await os.apiWithDialog('i/move', {
diff --git a/packages/frontend/src/pages/settings/mute-block.word-mute.vue b/packages/frontend/src/pages/settings/mute-block.word-mute.vue
index 7328967c51..a70adaf359 100644
--- a/packages/frontend/src/pages/settings/mute-block.word-mute.vue
+++ b/packages/frontend/src/pages/settings/mute-block.word-mute.vue
@@ -64,7 +64,7 @@ async function save() {
os.alert({
type: 'error',
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
throw err;
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index 766f33ff65..b1b1e4fb03 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.notificationRecieveConfig }}</template>
<div class="_gaps_s">
<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>
{{
$i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none :
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index e2d98cbf29..3e707041eb 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -206,7 +206,7 @@ function changeAvatar(ev) {
const { canceled } = await os.confirm({
type: 'question',
- text: i18n.t('cropImageAsk'),
+ text: i18n.ts.cropImageAsk,
okText: i18n.ts.cropYes,
cancelText: i18n.ts.cropNo,
});
@@ -232,7 +232,7 @@ function changeBanner(ev) {
const { canceled } = await os.confirm({
type: 'question',
- text: i18n.t('cropImageAsk'),
+ text: i18n.ts.cropImageAsk,
okText: i18n.ts.cropYes,
cancelText: i18n.ts.cropNo,
});
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index 9fbcce2286..a116a0407f 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.sounds }}</template>
<div class="_gaps_s">
<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>
<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>
import { Ref, computed, ref } from 'vue';
+import XSound from './sounds.sound.vue';
import type { SoundType, OperationType } from '@/scripts/sound.js';
import type { SoundStore } from '@/store.js';
-import XSound from './sounds.sound.vue';
import MkRange from '@/components/MkRange.vue';
import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
diff --git a/packages/frontend/src/pages/settings/theme.install.vue b/packages/frontend/src/pages/settings/theme.install.vue
index 45970c88e6..2946a43398 100644
--- a/packages/frontend/src/pages/settings/theme.install.vue
+++ b/packages/frontend/src/pages/settings/theme.install.vue
@@ -33,7 +33,7 @@ async function install(code: string): Promise<void> {
await installTheme(code);
os.alert({
type: 'success',
- text: i18n.t('_theme.installed', { name: theme.name }),
+ text: i18n.tsx._theme.installed({ name: theme.name }),
});
} catch (err) {
switch (err.message.toLowerCase()) {
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index a122c4c819..d079d0f92b 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -99,7 +99,7 @@ async function save(): Promise<void> {
async function del(): Promise<void> {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('deleteAreYouSure', { x: webhook.name }),
+ text: i18n.tsx.deleteAreYouSure({ x: webhook.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue
index 3f007b7afc..4e99ef5ae7 100644
--- a/packages/frontend/src/pages/signup-complete.vue
+++ b/packages/frontend/src/pages/signup-complete.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ti ti-user-check"></i>
</div>
<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>
<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"/>
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index e14bd6d89b..738b015a99 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -208,7 +208,7 @@ async function saveAs() {
changed.value = false;
os.alert({
type: 'success',
- text: i18n.t('_theme.installed', { name: theme.value.name }),
+ text: i18n.tsx._theme.installed({ name: theme.value.name }),
});
}
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index ed9722b7ed..e8687b148b 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</dl>
<dl v-if="user.birthday" class="field">
<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 class="field">
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index 59c46c2cbc..91b1218527 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -66,7 +66,7 @@ function addApp() {
async function deleteFile(file: Misskey.entities.DriveFile) {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('driveFileDeleteConfirm', { name: file.name }),
+ text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }),
});
if (canceled) return;
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index 110be244cb..bfc3c4a8f1 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -47,7 +47,7 @@ export async function getNoteClipMenu(props: {
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
const confirm = await os.confirm({
type: 'warning',
- text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
+ text: i18n.tsx.confirmToUnclipAlreadyClippedNote({ name: clip.name }),
});
if (!confirm.canceled) {
os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
@@ -231,7 +231,7 @@ export function getNoteMenu(props: {
function share(): void {
navigator.share({
- title: i18n.t('noteOf', { user: appearNote.user.name }),
+ title: i18n.tsx.noteOf({ user: appearNote.user.name }),
text: appearNote.text,
url: `${url}/notes/${appearNote.id}`,
});
diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/scripts/get-note-summary.ts
index 1fd9f04d46..2007e0ea97 100644
--- a/packages/frontend/src/scripts/get-note-summary.ts
+++ b/packages/frontend/src/scripts/get-note-summary.ts
@@ -30,7 +30,7 @@ export const getNoteSummary = (note: Misskey.entities.Note): string => {
// ファイルが添付されているとき
if ((note.files || []).length !== 0) {
- summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`;
+ summary += ` (${i18n.tsx.withNFiles({ n: note.files.length })})`;
}
// 投票が添付されているとき
diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts
index 3366f3eac3..6aa1468e87 100644
--- a/packages/frontend/src/scripts/i18n.ts
+++ b/packages/frontend/src/scripts/i18n.ts
@@ -14,37 +14,39 @@ type FlattenKeys<T extends ILocale, TPrediction> = keyof {
: never]: T[K];
};
-type ParametersOf<T extends ILocale, TKey extends FlattenKeys<T, ParameterizedString<string>>> = T extends ILocale
- ? TKey extends `${infer K}.${infer C}`
- // @ts-expect-error -- C は明らかに FlattenKeys<T[K], ParameterizedString<string>> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
- ? ParametersOf<T[K], C>
- : TKey extends keyof T
- ? T[TKey] extends ParameterizedString<infer P>
- ? P
- : never
+type ParametersOf<T extends ILocale, TKey extends FlattenKeys<T, ParameterizedString>> = TKey extends `${infer K}.${infer C}`
+ // @ts-expect-error -- C は明らかに FlattenKeys<T[K], ParameterizedString> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
+ ? ParametersOf<T[K], C>
+ : TKey extends keyof T
+ ? T[TKey] extends ParameterizedString<infer P>
+ ? P
: never
- : never;
+ : never;
-type Ts<T extends ILocale> = {
- readonly [K in keyof T as T[K] extends ParameterizedString<string> ? never : K]: T[K] extends ILocale ? Ts<T[K]> : string;
+type Tsx<T extends ILocale> = {
+ 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> {
- constructor(private locale: T) {
+ private tsxCache?: Tsx<T>;
+
+ constructor(public locale: T) {
//#region BIND
this.t = this.t.bind(this);
//#endregion
}
- public get ts(): Ts<T> {
+ public get ts(): T {
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 {
const value = target[p as keyof TTarget];
if (typeof value === 'object') {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 実際には null がくることはないので。
- return new Proxy(value!, new Handler<TTarget[keyof TTarget] & object>());
+ return new Proxy(value, new Handler<TTarget[keyof TTarget] & ILocale>());
}
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, 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 }) {
- let str: string | ParameterizedString<string> | ILocale = this.locale;
+ let str: string | ParameterizedString | ILocale = this.locale;
for (const k of key.split('.')) {
str = str[k];
@@ -113,3 +244,51 @@ export class I18n<T extends ILocale> {
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');
+ });
+ });
+}
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 304ebbf0b2..f58756b284 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -189,7 +189,7 @@ const addColumn = async (ev) => {
const { canceled, result: column } = await os.select({
title: i18n.ts._deck.addColumn,
items: columns.map(column => ({
- value: column, text: i18n.t('_deck._columns.' + column),
+ value: column, text: i18n.ts._deck._columns[column],
})),
});
if (canceled) return;
@@ -197,7 +197,7 @@ const addColumn = async (ev) => {
addColumnToStore({
type: column,
id: uuid(),
- name: i18n.t('_deck._columns.' + column),
+ name: i18n.ts._deck._columns[column],
width: 330,
});
};
@@ -256,7 +256,7 @@ function changeProfile(ev: MouseEvent) {
async function deleteProfile() {
const { canceled } = await os.confirm({
type: 'warning',
- text: i18n.t('deleteAreYouSure', { x: deckStore.state.profile }),
+ text: i18n.tsx.deleteAreYouSure({ x: deckStore.state.profile }),
});
if (canceled) return;
diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue
index c78e291a2e..5bd16584d2 100644
--- a/packages/frontend/src/widgets/WidgetCalendar.vue
+++ b/packages/frontend/src/widgets/WidgetCalendar.vue
@@ -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.calendar, { [$style.isHoliday]: isHoliday }]">
<p :class="$style.monthAndYear">
- <span :class="$style.year">{{ i18n.t('yearX', { year }) }}</span>
- <span :class="$style.month">{{ i18n.t('monthX', { month }) }}</span>
+ <span :class="$style.year">{{ i18n.tsx.yearX({ year }) }}</span>
+ <span :class="$style.month">{{ i18n.tsx.monthX({ month }) }}</span>
</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-else :class="$style.day">{{ i18n.t('dayX', { day }) }}</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.tsx.dayX({ day }) }}</p>
<p :class="$style.weekDay">{{ weekDay }}</p>
</div>
<div :class="$style.info">
diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue
index 94bf6d7eec..f2fc24ddf7 100644
--- a/packages/frontend/src/widgets/WidgetSlideshow.vue
+++ b/packages/frontend/src/widgets/WidgetSlideshow.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<p v-if="widgetProps.folderId == null">
{{ i18n.ts.folder }}
</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="slideB" class="slide b"></div>
</div>
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index 8f46bc0206..f1e4897634 100644
--- a/packages/frontend/src/widgets/WidgetTimeline.vue
+++ b/packages/frontend/src/widgets/WidgetTimeline.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<template #header>
<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>
</button>
</template>
diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue
index 35925a9088..65d9235565 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-for="stat in stats" :key="stat.tag">
<div class="tag">
<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>
<MkMiniChart class="chart" :src="stat.chart"/>
</div>