From ac2bace76408d71a9bd263322e8fd9e9e4d20f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 28 Dec 2023 10:27:12 +0900 Subject: [PATCH 001/334] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 106cf3ce15..30e2e57b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ ### General - v2023.12.1でDockerを利用してサーバーを起動できない問題を修正 +### Client +- Enhance: 検索画面においてEnterキー押下で検索できるように + ## 2023.12.1 ### Note @@ -124,7 +127,6 @@ - Fix: WebKitブラウザー上でも「デバイスの画面を常にオンにする」機能が効くように - Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正 - Fix: MFMでルビの中のテキストがnyaizeされない問題を修正 -- Enhance: 検索画面においてEnterキー押下で検索できるように ### Server - Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように From 7ca0af9e7e2a325171d2f26414165078af5d5249 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Thu, 28 Dec 2023 13:40:57 +0900 Subject: [PATCH 002/334] =?UTF-8?q?chore(misskey-js):=20`build-misskey-js-?= =?UTF-8?q?with-types`=E6=99=82=E3=81=AB`api-extractor`=E3=82=92=E8=B5=B0?= =?UTF-8?q?=E3=82=89=E3=81=9B=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#1283?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7579323bbe..49de5a5efd 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "build-assets": "node ./scripts/build-assets.mjs", "build": "pnpm build-pre && pnpm -r build && pnpm build-assets", "build-storybook": "pnpm --filter frontend build-storybook", - "build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build", + "build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api", "start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js", "start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js", "init": "pnpm migrate", From 8fb8d7c10caac6696a9364beb3457521f3966c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 29 Dec 2023 18:22:40 +0900 Subject: [PATCH 003/334] =?UTF-8?q?enhance(frontend):=20=E3=83=8F=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=82=BF=E3=82=B0=E5=85=A5=E5=8A=9B=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=80=81=E6=9C=AC=E6=96=87=E3=81=AE=E6=9C=AB=E5=B0=BE?= =?UTF-8?q?=E3=81=AE=E8=A1=8C=E3=81=AB=E4=BD=95=E3=82=82=E6=9B=B8=E3=81=8B?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E6=96=B0=E3=81=9F=E3=81=AB=E3=82=B9=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=20(#12851)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (enhance) ハッシュタグ入力時に、本文の末尾の行に何も書かれていないならスペースを追記しない * Updahe Changelog --- CHANGELOG.md | 5 +++++ packages/frontend/src/components/MkPostForm.vue | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e2e57b7d..ea34fa9ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ --> +## 202x.x.x (Unreleased) + +### Client +- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように + ## 2023.12.2 ### General diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 3aacf4c2da..b86f50eac6 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -752,7 +752,17 @@ async function post(ev?: MouseEvent) { if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') { const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_; + if (!postData.text) { + postData.text = hashtags_; + } else { + const postTextLines = postData.text.split('\n'); + if (postTextLines[postTextLines.length - 1].trim() === '') { + postTextLines[postTextLines.length - 1] += hashtags_; + } else { + postTextLines[postTextLines.length - 1] += ' ' + hashtags_; + } + postData.text = postTextLines.join('\n'); + } } // plugin From 7948018e6a4735fc32d61e8690319802e38baf3a Mon Sep 17 00:00:00 2001 From: MomentQYC <62551256+MomentQYC@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:23:29 +0800 Subject: [PATCH 004/334] feat: Add support for TrueMail (#12850) Co-authored-by: MarryDream <2190758465@qq.com> --- .../1703658526000-supportTrueMailApi.js | 20 ++++++ packages/backend/src/core/EmailService.ts | 69 ++++++++++++++++++- packages/backend/src/models/Meta.ts | 17 +++++ .../src/server/api/endpoints/admin/meta.ts | 15 ++++ .../server/api/endpoints/admin/update-meta.ts | 23 +++++++ .../frontend/src/pages/admin/security.vue | 17 +++++ 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1703658526000-supportTrueMailApi.js diff --git a/packages/backend/migration/1703658526000-supportTrueMailApi.js b/packages/backend/migration/1703658526000-supportTrueMailApi.js new file mode 100644 index 0000000000..0054d54122 --- /dev/null +++ b/packages/backend/migration/1703658526000-supportTrueMailApi.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SupportTrueMailApi1703658526000 { + name = 'SupportTrueMailApi1703658526000' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "truemailInstance" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "truemailAuthKey" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "enableTruemailApi" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTruemailApi"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailInstance"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailAuthKey"`); + } +} diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts index 7fc7800783..7e812b4df2 100644 --- a/packages/backend/src/core/EmailService.ts +++ b/packages/backend/src/core/EmailService.ts @@ -156,7 +156,7 @@ export class EmailService { @bindThis public async validateEmailForAccount(emailAddress: string): Promise<{ available: boolean; - reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned'; + reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist'; }> { const meta = await this.metaService.fetch(); @@ -173,6 +173,8 @@ export class EmailService { if (meta.enableActiveEmailValidation) { if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) { validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey); + } else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) { + validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey); } else { validated = await validateEmail({ email: emailAddress, @@ -201,6 +203,8 @@ export class EmailService { validated.reason === 'disposable' ? 'disposable' : validated.reason === 'mx' ? 'mx' : validated.reason === 'smtp' ? 'smtp' : + validated.reason === 'network' ? 'network' : + validated.reason === 'blacklist' ? 'blacklist' : null, }; } @@ -265,4 +269,67 @@ export class EmailService { reason: null, }; } + + private async trueMail(truemailInstance: string, emailAddress: string, truemailAuthKey: string): Promise<{ + valid: boolean; + reason: 'used' | 'format' | 'blacklist' | 'mx' | 'smtp' | 'network' | T | null; + }> { + const endpoint = truemailInstance + '?email=' + emailAddress; + try { + const res = await this.httpRequestService.send(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: truemailAuthKey + }, + }); + + const json = (await res.json()) as { + email: string; + success: boolean; + errors?: { + list_match?: string; + regex?: string; + mx?: string; + smtp?: string; + } | null; + }; + + if (json.email === undefined || (json.email !== undefined && json.errors?.regex)) { + return { + valid: false, + reason: 'format', + }; + } + if (json.errors?.smtp) { + return { + valid: false, + reason: 'smtp', + }; + } + if (json.errors?.mx) { + return { + valid: false, + reason: 'mx', + }; + } + if (!json.success) { + return { + valid: false, + reason: json.errors?.list_match as T || 'blacklist', + }; + } + + return { + valid: true, + reason: null, + }; + } catch (error) { + return { + valid: false, + reason: 'network', + }; + } + } } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 84ca762492..f5a75ed28a 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -457,6 +457,23 @@ export class MiMeta { }) public verifymailAuthKey: string | null; + @Column('boolean', { + default: false, + }) + public enableTruemailApi: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public truemailInstance: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public truemailAuthKey: string | null; + @Column('boolean', { default: true, }) diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index febc4ab1b1..281f6c484c 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -284,6 +284,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTruemailApi: { + type: 'boolean', + optional: false, nullable: false, + }, + truemailInstance: { + type: 'string', + optional: false, nullable: true, + }, + truemailAuthKey: { + type: 'string', + optional: false, nullable: true, + }, enableChartsForRemoteUser: { type: 'boolean', optional: false, nullable: false, @@ -520,6 +532,9 @@ export default class extends Endpoint { // eslint- enableActiveEmailValidation: instance.enableActiveEmailValidation, enableVerifymailApi: instance.enableVerifymailApi, verifymailAuthKey: instance.verifymailAuthKey, + enableTruemailApi: instance.enableTruemailApi, + truemailInstance: instance.truemailInstance, + truemailAuthKey: instance.truemailAuthKey, enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, enableServerMachineStats: instance.enableServerMachineStats, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 5a215696fb..3a6426435d 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -116,6 +116,9 @@ export const paramDef = { enableActiveEmailValidation: { type: 'boolean' }, enableVerifymailApi: { type: 'boolean' }, verifymailAuthKey: { type: 'string', nullable: true }, + enableTruemailApi: { type: 'boolean' }, + truemailInstance: { type: 'string', nullable: true }, + truemailAuthKey: { type: 'string', nullable: true }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, enableServerMachineStats: { type: 'boolean' }, @@ -469,6 +472,26 @@ export default class extends Endpoint { // eslint- set.verifymailAuthKey = ps.verifymailAuthKey; } } + + if (ps.enableTruemailApi !== undefined) { + set.enableTruemailApi = ps.enableTruemailApi; + } + + if (ps.truemailInstance !== undefined) { + if (ps.truemailInstance === '') { + set.truemailInstance = null; + } else { + set.truemailInstance = ps.truemailInstance; + } + } + + if (ps.truemailAuthKey !== undefined) { + if (ps.truemailAuthKey === '') { + set.truemailAuthKey = null; + } else { + set.truemailAuthKey = ps.truemailAuthKey; + } + } if (ps.enableChartsForRemoteUser !== undefined) { set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser; diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue index 7070157ca9..8d79dea20f 100644 --- a/packages/frontend/src/pages/admin/security.vue +++ b/packages/frontend/src/pages/admin/security.vue @@ -80,6 +80,17 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + + + + @@ -153,6 +164,9 @@ const enableIpLogging = ref(false); const enableActiveEmailValidation = ref(false); const enableVerifymailApi = ref(false); const verifymailAuthKey = ref(null); +const enableTruemailApi = ref(false); +const truemailInstance = ref(null); +const truemailAuthKey = ref(null); const bannedEmailDomains = ref(''); async function init() { @@ -194,6 +208,9 @@ function save() { enableActiveEmailValidation: enableActiveEmailValidation.value, enableVerifymailApi: enableVerifymailApi.value, verifymailAuthKey: verifymailAuthKey.value, + enableTruemailApi: enableTruemailApi.value, + truemailInstance: truemailInstance.value, + truemailAuthKey: truemailAuthKey.value, bannedEmailDomains: bannedEmailDomains.value.split('\n'), }).then(() => { fetchInstance(); From 30594dde181e9d151542c41c6f09e673fcbb3124 Mon Sep 17 00:00:00 2001 From: woxtu Date: Fri, 29 Dec 2023 22:50:03 +0900 Subject: [PATCH 005/334] Fix a typo (#12853) --- COPYING | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COPYING b/COPYING index c218443d42..905d3e1236 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Unless otherwise stated this repository is -Copyright © 2014-2023 syuilo and contributers +Copyright © 2014-2023 syuilo and contributors And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE. From c0466d1585d37a32537e545ee9df3b990da44e85 Mon Sep 17 00:00:00 2001 From: woxtu Date: Sun, 31 Dec 2023 07:51:58 +0900 Subject: [PATCH 006/334] Convert symbols to strings explicitly (#12844) --- packages/frontend/src/pizzax.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index b2254a0611..d26899dbde 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -168,7 +168,7 @@ export class Storage { this.reactiveState[key].value = this.state[key] = rawValue; return this.addIdbSetJob(async () => { - if (_DEV_) console.log(`set ${key} start`); + if (_DEV_) console.log(`set ${String(key)} start`); switch (this.def[key].where) { case 'device': { this.pizzaxChannel.postMessage({ @@ -207,7 +207,7 @@ export class Storage { break; } } - if (_DEV_) console.log(`set ${key} complete`); + if (_DEV_) console.log(`set ${String(key)} complete`); }); } From 2a3398181104e8cfa1aed84badd2fdf64428ec2b Mon Sep 17 00:00:00 2001 From: tamaina Date: Sun, 31 Dec 2023 09:45:35 +0900 Subject: [PATCH 007/334] chore: use summaly, browser-image-resizer, and sharp-read-bmp on registry.npmjs.org instead of git (#12856) * chore: use @misskey-dev/summaly on registry.npmjs.org instead of git * fix backend dependency * fic backend dependency * @misskey-dev/sharp-read-bmp * fix * use @misskey-dev/browser-image-resizer --- packages/backend/package.json | 4 +- packages/backend/src/core/DriveService.ts | 2 +- .../backend/src/server/FileServerService.ts | 2 +- .../src/server/web/UrlPreviewService.ts | 2 +- packages/frontend/package.json | 4 +- .../frontend/src/components/MkUrlPreview.vue | 2 +- packages/frontend/src/scripts/upload.ts | 2 +- .../src/scripts/upload/compress-config.ts | 4 +- packages/frontend/test/url-preview.test.ts | 2 +- pnpm-lock.yaml | 130 ++++++------------ 10 files changed, 54 insertions(+), 100 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 4d1e9936aa..710412c43d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -74,6 +74,8 @@ "@fastify/multipart": "8.0.0", "@fastify/static": "6.12.0", "@fastify/view": "8.2.0", + "@misskey-dev/sharp-read-bmp": "^1.1.1", + "@misskey-dev/summaly": "^5.0.3", "@nestjs/common": "10.2.10", "@nestjs/core": "10.2.10", "@nestjs/testing": "10.2.10", @@ -157,11 +159,9 @@ "sanitize-html": "2.11.0", "secure-json-parse": "2.7.0", "sharp": "0.32.6", - "sharp-read-bmp": "github:misskey-dev/sharp-read-bmp", "slacc": "0.0.10", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "summaly": "github:misskey-dev/summaly", "systeminformation": "5.21.20", "tinycolor2": "1.6.0", "tmp": "0.2.1", diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 484f4fc52e..04f0e38e6f 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto'; import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import sharp from 'sharp'; -import { sharpBmp } from 'sharp-read-bmp'; +import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import { IsNull } from 'typeorm'; import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 0c7fc8cefe..f59996ce17 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -9,7 +9,7 @@ import { dirname } from 'node:path'; import { Inject, Injectable } from '@nestjs/common'; import rename from 'rename'; import sharp from 'sharp'; -import { sharpBmp } from 'sharp-read-bmp'; +import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import type { Config } from '@/config.js'; import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index d590244e34..3fd88355dd 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -4,7 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { summaly } from 'summaly'; +import { summaly } from '@misskey-dev/summaly'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { MetaService } from '@/core/MetaService.js'; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 523fc281b3..d3c655b0db 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -19,6 +19,7 @@ "dependencies": { "@discordapp/twemoji": "15.0.2", "@github/webauthn-json": "2.1.1", + "@misskey-dev/browser-image-resizer": "2.2.1-misskey.10", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/pluginutils": "5.1.0", @@ -30,7 +31,6 @@ "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6", "astring": "1.8.6", "broadcast-channel": "7.0.0", - "browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3", "buraha": "0.0.1", "canvas-confetti": "1.6.1", "chart.js": "4.4.1", @@ -74,6 +74,7 @@ "vuedraggable": "next" }, "devDependencies": { + "@misskey-dev/summaly": "^5.0.3", "@storybook/addon-actions": "7.6.5", "@storybook/addon-essentials": "7.6.5", "@storybook/addon-interactions": "7.6.5", @@ -127,7 +128,6 @@ "start-server-and-test": "2.0.3", "storybook": "7.6.5", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", - "summaly": "github:misskey-dev/summaly", "vite-plugin-turbosnap": "1.0.3", "vitest": "0.34.6", "vitest-fetch-mock": "0.2.2", diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index f0f1a13d0b..54f23780c2 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index baee85866c..9cf4be778c 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -527,6 +527,10 @@ export const routes = [{ path: '/clicker', component: page(() => import('./pages/clicker.vue')), loginRequired: true, +}, { + path: '/drop-and-fusion', + component: page(() => import('./pages/drop-and-fusion.vue')), + loginRequired: true, }, { path: '/timeline', component: page(() => import('./pages/timeline.vue')), diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 0b966ff199..acde78f5fd 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -92,7 +92,13 @@ export type OperationType = typeof operationTypes[number]; * @param soundStore サウンド設定 * @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする */ -export async function loadAudio(soundStore: SoundStore, options?: { useCache?: boolean; }) { +export async function loadAudio(soundStore: { + type: Exclude; +} | { + type: '_driveFile_'; + fileId: string; + fileUrl: string; +}, options?: { useCache?: boolean; }) { if (_DEV_) console.log('loading audio. opts:', options); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) { @@ -179,18 +185,31 @@ export async function playFile(soundStore: SoundStore) { createSourceNode(buffer, soundStore.volume)?.start(); } -export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null { +export async function playRaw(type: Exclude, volume = 1, pan = 0, playbackRate = 1) { + const buffer = await loadAudio({ type }); + if (!buffer) return; + createSourceNode(buffer, volume, pan, playbackRate)?.start(); +} + +export function createSourceNode(buffer: AudioBuffer, volume: number, pan = 0, playbackRate = 1) : AudioBufferSourceNode | null { const masterVolume = defaultStore.state.sound_masterVolume; if (isMute() || masterVolume === 0 || volume === 0) { return null; } + const panNode = ctx.createStereoPanner(); + panNode.pan.value = pan; + const gainNode = ctx.createGain(); gainNode.gain.value = masterVolume * volume; const soundSource = ctx.createBufferSource(); soundSource.buffer = buffer; - soundSource.connect(gainNode).connect(ctx.destination); + soundSource.playbackRate.value = playbackRate; + soundSource + .connect(panNode) + .connect(gainNode) + .connect(ctx.destination); return soundSource; } diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index b970ff1df4..e50002dc2c 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -27,6 +27,11 @@ function toolsMenuItems(): MenuItem[] { to: '/clicker', text: '🍪👈', icon: 'ti ti-cookie', + }, { + type: 'link', + to: '/drop-and-fusion', + text: 'Drop & Fusion', + icon: 'ti ti-apple', }, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? { type: 'link', to: '/custom-emojis-manager', From 9eae82de1d4f9157602451e26e734c8f4ae94bea Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sat, 6 Jan 2024 13:33:56 +0100 Subject: [PATCH 036/334] chore(dependabot) open-pull-requests-limit=10? Somehow it's not opening any PR, so try higher count --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c5755315fc..d4678ec5e0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,7 +17,7 @@ updates: directory: "/" schedule: interval: daily - open-pull-requests-limit: 5 + open-pull-requests-limit: 10 # List dependencies required to be updated together, sharing the same version numbers. # Those who simply have the common owner (e.g. @fastify) don't need to be listed. groups: From 0815a5235d226434e17ead0166227f5ec60133b8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 09:24:04 +0900 Subject: [PATCH 037/334] tweak game --- .../assets/drop-and-fusion/dropper.png | Bin 0 -> 32415 bytes .../assets/drop-and-fusion/frame-dark.svg | 28 ++++++ .../assets/drop-and-fusion/frame-light.svg | 28 ++++++ .../frontend/assets/drop-and-fusion/frame.svg | 25 ------ .../frontend/src/pages/drop-and-fusion.vue | 80 ++++++++++++++---- 5 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 packages/frontend/assets/drop-and-fusion/dropper.png create mode 100644 packages/frontend/assets/drop-and-fusion/frame-dark.svg create mode 100644 packages/frontend/assets/drop-and-fusion/frame-light.svg delete mode 100644 packages/frontend/assets/drop-and-fusion/frame.svg diff --git a/packages/frontend/assets/drop-and-fusion/dropper.png b/packages/frontend/assets/drop-and-fusion/dropper.png new file mode 100644 index 0000000000000000000000000000000000000000..f4300aa5c06f6001a87c01f8525847b294f43be3 GIT binary patch literal 32415 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 I=akR{02N9P_y7O^ literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/frame-dark.svg b/packages/frontend/assets/drop-and-fusion/frame-dark.svg new file mode 100644 index 0000000000..3fa7c0da81 --- /dev/null +++ b/packages/frontend/assets/drop-and-fusion/frame-dark.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/frontend/assets/drop-and-fusion/frame-light.svg b/packages/frontend/assets/drop-and-fusion/frame-light.svg new file mode 100644 index 0000000000..6052ccbaa0 --- /dev/null +++ b/packages/frontend/assets/drop-and-fusion/frame-light.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/frontend/assets/drop-and-fusion/frame.svg b/packages/frontend/assets/drop-and-fusion/frame.svg deleted file mode 100644 index 4276dae833..0000000000 --- a/packages/frontend/assets/drop-and-fusion/frame.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index d0ca5157ef..7f4a885b44 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -11,7 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- SCORE: +
SCORE:
+
HIGH SCORE: -
@@ -33,7 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- + +
{{ comboPrev }} Chain!
+ (); const canvasEl = shallowRef(); @@ -191,7 +196,7 @@ const FRUITS = [{ const GAME_WIDTH = 450; const GAME_HEIGHT = 600; -const PHYSICS_QUALITY_FACTOR = 32; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる +const PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる let viewScaleX = 1; let viewScaleY = 1; @@ -203,6 +208,7 @@ const comboPrev = ref(0); const dropReady = ref(true); const gameOver = ref(false); const gameStarted = ref(false); +const highScore = ref(null); class Game extends EventEmitter<{ changeScore: (score: number) => void; @@ -251,6 +257,8 @@ class Game extends EventEmitter<{ this.emit('changeScore', value); } + private comboIntervalId: number | null = null; + constructor() { super(); @@ -294,6 +302,8 @@ class Game extends EventEmitter<{ //#region walls const WALL_OPTIONS: Matter.IChamferableBodyDefinition = { isStatic: true, + friction: 0.7, + slop: 1.0, render: { strokeStyle: 'transparent', fillStyle: 'transparent', @@ -308,7 +318,7 @@ class Game extends EventEmitter<{ ]); //#endregion - this.overflowCollider = Matter.Bodies.rectangle(GAME_WIDTH / 2, 0, GAME_WIDTH, 125, { + this.overflowCollider = Matter.Bodies.rectangle(GAME_WIDTH / 2, 0, GAME_WIDTH, 200, { isStatic: true, isSensor: true, render: { @@ -328,11 +338,13 @@ class Game extends EventEmitter<{ private createBody(fruit: typeof FRUITS[number], x: number, y: number) { return Matter.Bodies.circle(x, y, fruit.size / 2, { label: fruit.id, - density: 0.0005, + //density: 0.0005, + density: fruit.size / 1000, + restitution: 0.2, frictionAir: 0.01, - restitution: 0.4, - friction: 0.5, + friction: 0.7, frictionStatic: 5, + slop: 1.0, //mass: 0, render: { sprite: { @@ -372,7 +384,7 @@ class Game extends EventEmitter<{ this.activeBodyIds.push(body.id); }, 100); - const additionalScore = Math.round(currentFruit.score * (1 + (this.combo / 3))); + const additionalScore = Math.round(currentFruit.score * (1 + ((this.combo - 1) / 3))); this.score += additionalScore; const pan = ((newX / GAME_WIDTH) - 0.5) * 2; @@ -449,7 +461,7 @@ class Game extends EventEmitter<{ } }); - window.setInterval(() => { + this.comboIntervalId = window.setInterval(() => { if (this.latestFusionedAt < Date.now() - this.COMBO_INTERVAL) { this.combo = 0; } @@ -469,7 +481,7 @@ class Game extends EventEmitter<{ this.emit('changeStock', this.stock); const x = Math.min(GAME_WIDTH - this.PLAYAREA_MARGIN - (st.fruit.size / 2), Math.max(this.PLAYAREA_MARGIN + (st.fruit.size / 2), _x)); - const body = this.createBody(st.fruit, x, st.fruit.size / 2); + const body = this.createBody(st.fruit, x, 50 + st.fruit.size / 2); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); this.latestDroppedBodyId = body.id; @@ -480,6 +492,7 @@ class Game extends EventEmitter<{ } public dispose() { + if (this.comboIntervalId) window.clearInterval(this.comboIntervalId); Matter.Render.stop(this.render); Matter.Runner.stop(this.runner); Matter.World.clear(this.engine.world, false); @@ -567,10 +580,28 @@ function attachGame() { currentPick.value = null; dropReady.value = false; gameOver.value = true; + + if (score.value > (highScore.value ?? 0)) { + highScore.value = score.value; + + misskeyApi('i/registry/set', { + scope: ['dropAndFusionGame'], + key: 'highScore', + value: highScore.value, + }); + } }); } -onMounted(() => { +onMounted(async () => { + try { + highScore.value = await misskeyApi('i/registry/get', { + scope: ['dropAndFusionGame'], + key: 'highScore', + }); + } catch (err) { + } + game = new Game(); attachGame(); @@ -667,7 +698,9 @@ definePageMetadata({ top: 0; left: 0; width: 100%; - filter: drop-shadow(0 6px 16px #0007); + // なんかiOSでちらつく + //filter: drop-shadow(0 6px 16px #0007); + border-radius: 16px; pointer-events: none; user-select: none; } @@ -699,13 +732,28 @@ definePageMetadata({ text-align: center; font-weight: bold; font-style: oblique; + color: #fff; + -webkit-text-stroke: 1px rgb(255, 145, 0); + text-shadow: 0 0 6px #0005; pointer-events: none; user-select: none; } .currentFruit { position: absolute; - margin-top: 20px; + margin-top: 80px; + z-index: 2; + filter: drop-shadow(0 6px 16px #0007); + pointer-events: none; + user-select: none; +} + +.dropper { + position: absolute; + top: 0; + width: 70px; + margin-top: -10px; + margin-left: -30px; z-index: 2; filter: drop-shadow(0 6px 16px #0007); pointer-events: none; @@ -714,7 +762,7 @@ definePageMetadata({ .currentFruitArrow { position: absolute; - margin-top: 20px; + margin-top: 100px; z-index: 3; animation: currentFruitArrow 2s ease infinite; pointer-events: none; @@ -723,10 +771,10 @@ definePageMetadata({ .dropGuide { position: absolute; - top: 50px; + top: 120px; z-index: 3; width: 3px; - height: calc(100% - 50px); + height: calc(100% - 120px); background: #f002; pointer-events: none; user-select: none; From f2dee7b25eb473796ff77e2abfae88f174fd5b90 Mon Sep 17 00:00:00 2001 From: _ Date: Sun, 7 Jan 2024 09:57:01 +0900 Subject: [PATCH 038/334] =?UTF-8?q?Fix:=20=E3=83=AA=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E3=80=8C=E3=83=AA=E3=83=8E=E3=83=BC=E3=83=88=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=80=8D=E3=81=8C=E6=AD=A3=E3=81=97=E3=81=8F=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=81=97=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#12932)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: list timeline withRenotes * add CHANGELOG --- CHANGELOG.md | 1 + packages/backend/src/server/api/stream/channels/user-list.ts | 4 ++++ packages/frontend/src/components/MkTimeline.vue | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6e2db950..8c27349f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### General - Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加 +- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 ### Client - Feat: 新しいゲームを追加 diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 909b5a5e03..e0245814c4 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -21,6 +21,7 @@ class UserListChannel extends Channel { private membershipsMap: Record | undefined> = {}; private listUsersClock: NodeJS.Timeout; private withFiles: boolean; + private withRenotes: boolean; constructor( private userListsRepository: UserListsRepository, @@ -39,6 +40,7 @@ class UserListChannel extends Channel { public async init(params: any) { this.listId = params.listId as string; this.withFiles = params.withFiles ?? false; + this.withRenotes = params.withRenotes ?? true; // Check existence and owner const listExist = await this.userListsRepository.exist({ @@ -104,6 +106,8 @@ class UserListChannel extends Channel { } } + if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index d5adc02ca7..63f779dbde 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -132,6 +132,7 @@ function connectChannel() { connection.on('mention', onNote); } else if (props.src === 'list') { connection = stream.useChannel('userList', { + withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, listId: props.list, }); @@ -198,6 +199,7 @@ function updatePaginationQuery() { } else if (props.src === 'list') { endpoint = 'notes/user-list-timeline'; query = { + withRenotes: props.withRenotes, withFiles: props.onlyFiles ? true : undefined, listId: props.list, }; @@ -236,8 +238,9 @@ function refreshEndpointAndChannel() { updatePaginationQuery(); } +// デッキのリストカラムでwithRenotesを変更した場合に自動的に更新されるようにさせる // IDが切り替わったら切り替え先のTLを表示させたい -watch(() => [props.list, props.antenna, props.channel, props.role], refreshEndpointAndChannel); +watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], refreshEndpointAndChannel); // 初回表示用 refreshEndpointAndChannel(); From 4ea030d66916777595bf1429fab4d5c1b93d4a5d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 10:35:39 +0900 Subject: [PATCH 039/334] tweak game --- .../frontend/src/pages/drop-and-fusion.vue | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 7f4a885b44..6014931562 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
+
@@ -221,10 +221,10 @@ class Game extends EventEmitter<{ private COMBO_INTERVAL = 1000; public readonly DROP_INTERVAL = 500; private PLAYAREA_MARGIN = 25; + private STOCK_MAX = 4; private engine: Matter.Engine; private render: Matter.Render; private runner: Matter.Runner; - private detector: Matter.Detector; private overflowCollider: Matter.Body; private isGameOver = false; @@ -286,7 +286,7 @@ class Game extends EventEmitter<{ wireframeBackground: 'transparent', // transparent to hide wireframes: false, showSleeping: false, - pixelRatio: window.devicePixelRatio, + pixelRatio: Math.max(2, window.devicePixelRatio), }, }); @@ -295,8 +295,6 @@ class Game extends EventEmitter<{ this.runner = Matter.Runner.create(); Matter.Runner.run(this.runner, this.engine); - this.detector = Matter.Detector.create(); - this.engine.world.bodies = []; //#region walls @@ -412,7 +410,7 @@ class Game extends EventEmitter<{ } public start() { - for (let i = 0; i < 4; i++) { + for (let i = 0; i < this.STOCK_MAX; i++) { this.stock.push({ id: Math.random().toString(), fruit: FRUITS.filter(x => x.available)[Math.floor(Math.random() * FRUITS.filter(x => x.available).length)], @@ -423,8 +421,8 @@ class Game extends EventEmitter<{ // TODO: fusion予約状態のアイテムは光らせるなどの演出をすると楽しそう let fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = []; - const minCollisionDepthForSound = 2.5; - const maxCollisionDepthForSound = 9; + const minCollisionEnergyForSound = 2.5; + const maxCollisionEnergyForSound = 9; const soundPitchMax = 4; const soundPitchMin = 0.5; @@ -451,8 +449,8 @@ class Game extends EventEmitter<{ } } else { const energy = pairs.collision.depth; - if (energy > minCollisionDepthForSound) { - const vol = (Math.min(maxCollisionDepthForSound, energy - minCollisionDepthForSound) / maxCollisionDepthForSound) / 4; + if (energy > minCollisionEnergyForSound) { + const vol = (Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4; const pan = ((((bodyA.position.x + bodyB.position.x) / 2) / GAME_WIDTH) - 0.5) * 2; const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10))); sound.playRaw('syuilo/poi1', vol, pan, pitch); @@ -700,7 +698,6 @@ definePageMetadata({ width: 100%; // なんかiOSでちらつく //filter: drop-shadow(0 6px 16px #0007); - border-radius: 16px; pointer-events: none; user-select: none; } @@ -710,7 +707,8 @@ definePageMetadata({ display: block; z-index: 1; margin-top: -50px; - max-width: 100%; + width: 100% !important; + height: auto !important; pointer-events: none; user-select: none; } From 2a9db983fcd79e1993d5ea5b03e4979c1a578d7d Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sun, 7 Jan 2024 02:35:58 +0100 Subject: [PATCH 040/334] feat: export clips (#12931) * feat: export clips * Update CHANGELOG.md --- CHANGELOG.md | 1 + locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + packages/backend/src/core/QueueService.ts | 10 + .../backend/src/queue/QueueProcessorModule.ts | 2 + .../src/queue/QueueProcessorService.ts | 3 + .../processors/ExportClipsProcessorService.ts | 206 ++++++++++++++++++ .../backend/src/server/api/EndpointsModule.ts | 4 + packages/backend/src/server/api/endpoints.ts | 4 +- .../server/api/endpoints/i/export-clips.ts | 35 +++ packages/backend/test/e2e/exports.ts | 194 +++++++++++++++++ packages/backend/test/utils.ts | 2 +- .../src/pages/settings/import-export.vue | 12 + 13 files changed, 473 insertions(+), 2 deletions(-) create mode 100644 packages/backend/src/queue/processors/ExportClipsProcessorService.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-clips.ts create mode 100644 packages/backend/test/e2e/exports.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c27349f61..0d2fb4ccd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました - Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916) +- Enhance: クリップをエクスポートできるように ## 2023.12.2 diff --git a/locales/index.d.ts b/locales/index.d.ts index 99bc0fc04f..75517fa2ad 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2256,6 +2256,7 @@ export interface Locale { "_exportOrImport": { "allNotes": string; "favoritedNotes": string; + "clips": string; "followingList": string; "muteList": string; "blockingList": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 7cf5663a72..8b6b119d7e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2159,6 +2159,7 @@ _profile: _exportOrImport: allNotes: "全てのノート" favoritedNotes: "お気に入りにしたノート" + clips: "クリップ" followingList: "フォロー" muteList: "ミュート" blockingList: "ブロック" diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 4f99dee64e..dc3f248da4 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -182,6 +182,16 @@ export class QueueService { }); } + @bindThis + public createExportClipsJob(user: ThinUser) { + return this.dbQueue.add('exportClips', { + user: { id: user.id }, + }, { + removeOnComplete: true, + removeOnFail: true, + }); + } + @bindThis public createExportFavoritesJob(user: ThinUser) { return this.dbQueue.add('exportFavorites', { diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index e6327002c5..9c52c7d76a 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -24,6 +24,7 @@ import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmo import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js'; import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; +import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js'; import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js'; import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js'; import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js'; @@ -53,6 +54,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor DeleteDriveFilesProcessorService, ExportCustomEmojisProcessorService, ExportNotesProcessorService, + ExportClipsProcessorService, ExportFavoritesProcessorService, ExportFollowingProcessorService, ExportMutingProcessorService, diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index b872dd65f7..bcc1a69f80 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -16,6 +16,7 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; +import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js'; import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js'; import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js'; @@ -91,6 +92,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService, private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService, private exportNotesProcessorService: ExportNotesProcessorService, + private exportClipsProcessorService: ExportClipsProcessorService, private exportFavoritesProcessorService: ExportFavoritesProcessorService, private exportFollowingProcessorService: ExportFollowingProcessorService, private exportMutingProcessorService: ExportMutingProcessorService, @@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job); case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job); case 'exportNotes': return this.exportNotesProcessorService.process(job); + case 'exportClips': return this.exportClipsProcessorService.process(job); case 'exportFavorites': return this.exportFavoritesProcessorService.process(job); case 'exportFollowing': return this.exportFollowingProcessorService.process(job); case 'exportMuting': return this.exportMutingProcessorService.process(job); diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts new file mode 100644 index 0000000000..5221497bd3 --- /dev/null +++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts @@ -0,0 +1,206 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import { Writable } from 'node:stream'; +import { Inject, Injectable, StreamableFile } from '@nestjs/common'; +import { MoreThan } from 'typeorm'; +import { format as dateFormat } from 'date-fns'; +import { DI } from '@/di-symbols.js'; +import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { DriveService } from '@/core/DriveService.js'; +import { createTemp } from '@/misc/create-temp.js'; +import type { MiPoll } from '@/models/Poll.js'; +import type { MiNote } from '@/models/Note.js'; +import { bindThis } from '@/decorators.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { Packed } from '@/misc/json-schema.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; +import type { DbJobDataWithUser } from '../types.js'; + +@Injectable() +export class ExportClipsProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + private driveService: DriveService, + private queueLoggerService: QueueLoggerService, + private idService: IdService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('export-clips'); + } + + @bindThis + public async process(job: Bull.Job): Promise { + this.logger.info(`Exporting clips of ${job.data.user.id} ...`); + + const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); + if (user == null) { + return; + } + + // Create temp file + const [path, cleanup] = await createTemp(); + + this.logger.info(`Temp file is ${path}`); + + try { + const stream = Writable.toWeb(fs.createWriteStream(path, { flags: 'a' })); + const writer = stream.getWriter(); + writer.closed.catch(this.logger.error); + + await writer.write('['); + + await this.processClips(writer, user, job); + + await writer.write(']'); + await writer.close(); + + this.logger.succ(`Exported to: ${path}`); + + const fileName = 'clips-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); + + this.logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } + } + + async processClips(writer: WritableStreamDefaultWriter, user: MiUser, job: Bull.Job) { + let exportedClipsCount = 0; + let cursor: MiClip['id'] | null = null; + + while (true) { + const clips = await this.clipsRepository.find({ + where: { + userId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }); + + if (clips.length === 0) { + job.updateProgress(100); + break; + } + + cursor = clips.at(-1)?.id ?? null; + + for (const clip of clips) { + // Stringify but remove the last `]}` + const content = JSON.stringify(this.serializeClip(clip)).slice(0, -2); + const isFirst = exportedClipsCount === 0; + await writer.write(isFirst ? content : ',\n' + content); + + await this.processClipNotes(writer, clip.id); + + await writer.write(']}'); + exportedClipsCount++; + } + + const total = await this.clipsRepository.countBy({ + userId: user.id, + }); + + job.updateProgress(exportedClipsCount / total); + } + } + + async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise { + let exportedClipNotesCount = 0; + let cursor: MiClipNote['id'] | null = null; + + while (true) { + const clipNotes = await this.clipNotesRepository.find({ + where: { + clipId, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + relations: ['note', 'note.user'], + }) as (MiClipNote & { note: MiNote & { user: MiUser } })[]; + + if (clipNotes.length === 0) { + break; + } + + cursor = clipNotes.at(-1)?.id ?? null; + + for (const clipNote of clipNotes) { + let poll: MiPoll | undefined; + if (clipNote.note.hasPoll) { + poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id }); + } + const content = JSON.stringify(this.serializeClipNote(clipNote, poll)); + const isFirst = exportedClipNotesCount === 0; + await writer.write(isFirst ? content : ',\n' + content); + + exportedClipNotesCount++; + } + } + } + + private serializeClip(clip: MiClip): Record { + return { + id: clip.id, + name: clip.name, + description: clip.description, + lastClippedAt: clip.lastClippedAt?.toISOString(), + clipNotes: [], + }; + } + + private serializeClipNote(clip: MiClipNote & { note: MiNote & { user: MiUser } }, poll: MiPoll | undefined): Record { + return { + id: clip.id, + createdAt: this.idService.parse(clip.id).date.toISOString(), + note: { + id: clip.note.id, + text: clip.note.text, + createdAt: this.idService.parse(clip.note.id).date.toISOString(), + fileIds: clip.note.fileIds, + replyId: clip.note.replyId, + renoteId: clip.note.renoteId, + poll: poll, + cw: clip.note.cw, + visibility: clip.note.visibility, + visibleUserIds: clip.note.visibleUserIds, + localOnly: clip.note.localOnly, + reactionAcceptance: clip.note.reactionAcceptance, + uri: clip.note.uri, + url: clip.note.url, + user: { + id: clip.note.user.id, + name: clip.note.user.name, + username: clip.note.user.username, + host: clip.note.user.host, + uri: clip.note.user.uri, + }, + }, + }; + } +} diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 86a64d7121..a3a9805444 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -208,6 +208,7 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; import * as ep___i_exportMute from './endpoints/i/export-mute.js'; import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; +import * as ep___i_exportClips from './endpoints/i/export-clips.js'; import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; @@ -569,6 +570,7 @@ const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass: const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default }; const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default }; const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default }; +const $i_exportClips: Provider = { provide: 'ep:i/export-clips', useClass: ep___i_exportClips.default }; const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default }; const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default }; const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default }; @@ -934,6 +936,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportFollowing, $i_exportMute, $i_exportNotes, + $i_exportClips, $i_exportFavorites, $i_exportUserLists, $i_exportAntennas, @@ -1293,6 +1296,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportFollowing, $i_exportMute, $i_exportNotes, + $i_exportClips, $i_exportFavorites, $i_exportUserLists, $i_exportAntennas, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 41232091c6..bd8aa4af72 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Schema } from '@/misc/json-schema.js'; import { permissions } from 'misskey-js'; +import type { Schema } from '@/misc/json-schema.js'; import { RolePolicies } from '@/core/RoleService.js'; import * as ep___admin_meta from './endpoints/admin/meta.js'; @@ -209,6 +209,7 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; import * as ep___i_exportMute from './endpoints/i/export-mute.js'; import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; +import * as ep___i_exportClips from './endpoints/i/export-clips.js'; import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; @@ -568,6 +569,7 @@ const eps = [ ['i/export-following', ep___i_exportFollowing], ['i/export-mute', ep___i_exportMute], ['i/export-notes', ep___i_exportNotes], + ['i/export-clips', ep___i_exportClips], ['i/export-favorites', ep___i_exportFavorites], ['i/export-user-lists', ep___i_exportUserLists], ['i/export-antennas', ep___i_exportAntennas], diff --git a/packages/backend/src/server/api/endpoints/i/export-clips.ts b/packages/backend/src/server/api/endpoints/i/export-clips.ts new file mode 100644 index 0000000000..9435a2b23c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-clips.ts @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; + +export const meta = { + secure: true, + requireCredential: true, + limit: { + duration: ms('1day'), + max: 1, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportClipsJob(me); + }); + } +} diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts new file mode 100644 index 0000000000..9686f2b7fd --- /dev/null +++ b/packages/backend/test/e2e/exports.ts @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import { signup, api, startServer, startJobQueue, port, post } from '../utils.js'; +import type { INestApplicationContext } from '@nestjs/common'; +import type * as misskey from 'misskey-js'; + +describe('export-clips', () => { + let app: INestApplicationContext; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + + // XXX: Any better way to get the result? + async function pollFirstDriveFile() { + while (true) { + const files = (await api('/drive/files', {}, alice)).body; + if (!files.length) { + await new Promise(r => setTimeout(r, 100)); + continue; + } + if (files.length > 1) { + throw new Error('Too many files?'); + } + const file = (await api('/drive/files/show', { fileId: files[0].id }, alice)).body; + const res = await fetch(new URL(new URL(file.url).pathname, `http://127.0.0.1:${port}`)); + return await res.json(); + } + } + + beforeAll(async () => { + app = await startServer(); + await startJobQueue(); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + }, 1000 * 60 * 2); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(async () => { + // Clean all clips and files of alice + const clips = (await api('/clips/list', {}, alice)).body; + for (const clip of clips) { + const res = await api('/clips/delete', { clipId: clip.id }, alice); + if (res.status !== 204) { + throw new Error('Failed to delete clip'); + } + } + const files = (await api('/drive/files', {}, alice)).body; + for (const file of files) { + const res = await api('/drive/files/delete', { fileId: file.id }, alice); + if (res.status !== 204) { + throw new Error('Failed to delete file'); + } + } + }); + + test('basic export', async () => { + let res = await api('/clips/create', { + name: 'foo', + description: 'bar', + }, alice); + assert.strictEqual(res.status, 200); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'foo'); + assert.strictEqual(exported[0].description, 'bar'); + assert.strictEqual(exported[0].clipNotes.length, 0); + }); + + test('export with notes', async () => { + let res = await api('/clips/create', { + name: 'foo', + description: 'bar', + }, alice); + assert.strictEqual(res.status, 200); + const clip = res.body; + + const note1 = await post(alice, { + text: 'baz1', + }); + + const note2 = await post(alice, { + text: 'baz2', + poll: { + choices: ['sakura', 'izumi', 'ako'], + }, + }); + + for (const note of [note1, note2]) { + res = await api('/clips/add-note', { + clipId: clip.id, + noteId: note.id, + }, alice); + assert.strictEqual(res.status, 204); + } + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'foo'); + assert.strictEqual(exported[0].description, 'bar'); + assert.strictEqual(exported[0].clipNotes.length, 2); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1'); + assert.strictEqual(exported[0].clipNotes[1].note.text, 'baz2'); + assert.deepStrictEqual(exported[0].clipNotes[1].note.poll.choices[0], 'sakura'); + }); + + test('multiple clips', async () => { + let res = await api('/clips/create', { + name: 'kawaii', + description: 'kawaii', + }, alice); + assert.strictEqual(res.status, 200); + const clip1 = res.body; + + res = await api('/clips/create', { + name: 'yuri', + description: 'yuri', + }, alice); + assert.strictEqual(res.status, 200); + const clip2 = res.body; + + const note1 = await post(alice, { + text: 'baz1', + }); + + const note2 = await post(alice, { + text: 'baz2', + }); + + res = await api('/clips/add-note', { + clipId: clip1.id, + noteId: note1.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/clips/add-note', { + clipId: clip2.id, + noteId: note2.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'kawaii'); + assert.strictEqual(exported[0].clipNotes.length, 1); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1'); + assert.strictEqual(exported[1].name, 'yuri'); + assert.strictEqual(exported[1].clipNotes.length, 1); + assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2'); + }); + + test('Clipping other user\'s note', async () => { + let res = await api('/clips/create', { + name: 'kawaii', + description: 'kawaii', + }, alice); + assert.strictEqual(res.status, 200); + const clip = res.body; + + const note = await post(bob, { + text: 'baz', + visibility: 'followers', + }); + + res = await api('/clips/add-note', { + clipId: clip.id, + noteId: note.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'kawaii'); + assert.strictEqual(exported[0].clipNotes.length, 1); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz'); + assert.strictEqual(exported[0].clipNotes[0].note.user.username, 'bob'); + }); +}); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 46b8ea9cdd..7c9428d476 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -17,7 +17,7 @@ import { entities } from '../src/postgres.js'; import { loadConfig } from '../src/config.js'; import type * as misskey from 'misskey-js'; -export { server as startServer } from '@/boot/common.js'; +export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; interface UserToken { token: string; diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue index 990eff99c1..70d718f1ab 100644 --- a/packages/frontend/src/pages/settings/import-export.vue +++ b/packages/frontend/src/pages/settings/import-export.vue @@ -21,6 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.export }} + + + + + + {{ i18n.ts.export }} + +
@@ -157,6 +165,10 @@ const exportFavorites = () => { misskeyApi('i/export-favorites', {}).then(onExportSuccess).catch(onError); }; +const exportClips = () => { + misskeyApi('i/export-clips', {}).then(onExportSuccess).catch(onError); +}; + const exportFollowing = () => { misskeyApi('i/export-following', { excludeMuting: excludeMutingUsers.value, From 00e195f50bcc29ee28b6ae11f39b7661a10f2b16 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 13:19:10 +0900 Subject: [PATCH 041/334] tweak game --- locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../assets/drop-and-fusion/keycap_1.png | Bin 0 -> 29193 bytes .../assets/drop-and-fusion/keycap_10.png | Bin 0 -> 33717 bytes .../assets/drop-and-fusion/keycap_2.png | Bin 0 -> 32324 bytes .../assets/drop-and-fusion/keycap_3.png | Bin 0 -> 33127 bytes .../assets/drop-and-fusion/keycap_4.png | Bin 0 -> 31182 bytes .../assets/drop-and-fusion/keycap_5.png | Bin 0 -> 32745 bytes .../assets/drop-and-fusion/keycap_6.png | Bin 0 -> 32100 bytes .../assets/drop-and-fusion/keycap_7.png | Bin 0 -> 31318 bytes .../assets/drop-and-fusion/keycap_8.png | Bin 0 -> 32886 bytes .../assets/drop-and-fusion/keycap_9.png | Bin 0 -> 32483 bytes .../frontend/src/pages/drop-and-fusion.vue | 291 +++++++++++++++--- packages/frontend/src/router.ts | 2 +- packages/frontend/src/ui/_common_/common.ts | 4 +- 15 files changed, 245 insertions(+), 54 deletions(-) create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_1.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_10.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_2.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_3.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_4.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_5.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_6.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_7.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_8.png create mode 100644 packages/frontend/assets/drop-and-fusion/keycap_9.png diff --git a/locales/index.d.ts b/locales/index.d.ts index 75517fa2ad..8dfb81790e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1192,6 +1192,7 @@ export interface Locale { "decorate": string; "addMfmFunction": string; "enableQuickAddMfmFunction": string; + "bubbleGame": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8b6b119d7e..d92c5f9a14 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1189,6 +1189,7 @@ seasonalScreenEffect: "季節に応じた画面の演出" decorate: "デコる" addMfmFunction: "装飾を追加" enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する" +bubbleGame: "バブルゲーム" _announcement: forExistingUsers: "既存ユーザーのみ" diff --git a/packages/frontend/assets/drop-and-fusion/keycap_1.png b/packages/frontend/assets/drop-and-fusion/keycap_1.png new file mode 100644 index 0000000000000000000000000000000000000000..d672f2854a8e22451f7329a1603c3241615c5936 GIT binary patch literal 29193 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 literal 0 HcmV?d00001 diff --git a/packages/frontend/assets/drop-and-fusion/keycap_10.png b/packages/frontend/assets/drop-and-fusion/keycap_10.png new file mode 100644 index 0000000000000000000000000000000000000000..32cf19354065bc8b720e46553c4da1ba5cedf4ea GIT binary patch literal 33717 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
@@ -103,6 +104,7 @@ SPDX-License-Identifier: AGPL-3.0-only import * as Matter from 'matter-js'; import { Ref, onMounted, ref, shallowRef } from 'vue'; import { EventEmitter } from 'eventemitter3'; +import * as Misskey from 'misskey-js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import * as sound from '@/scripts/sound.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; @@ -115,6 +117,8 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { useInterval } from '@/scripts/use-interval.js'; import MkSelect from '@/components/MkSelect.vue'; +import { apiUrl } from '@/config.js'; +import { $i } from '@/account.js'; type Mono = { id: string; @@ -788,6 +792,46 @@ async function start() { game.start(); } +function getGameImageDriveFile() { + return new Promise(res => { + canvasEl.value?.toBlob(blob => { + if (!blob) return res(null); + if ($i == null) return res(null); + const formData = new FormData(); + formData.append('file', blob); + formData.append('name', `bubble-game-${Date.now()}.png`); + formData.append('isSensitive', 'false'); + formData.append('comment', 'null'); + formData.append('i', $i.token); + if (defaultStore.state.uploadFolder) { + formData.append('folderId', defaultStore.state.uploadFolder); + } + + window.fetch(apiUrl + '/drive/files/create', { + method: 'POST', + body: formData, + }) + .then(response => response.json()) + .then(f => { + res(f); + }); + }, 'image/png'); + }); +} + +async function share() { + const uploading = getGameImageDriveFile(); + os.promiseDialog(uploading); + const file = await uploading; + if (!file) return; + os.post({ + initialText: `#BubbleGame +MODE: ${gameMode.value} +SCORE: ${score.value}`, + initialFiles: [file], + }); +} + useInterval(() => { if (!canvasEl.value) return; const actualCanvasWidth = canvasEl.value.getBoundingClientRect().width; From c6a4caa8be576f9ac457bbb218eccb91455148aa Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Jan 2024 14:32:57 +0900 Subject: [PATCH 044/334] refactor --- .../frontend/src/pages/drop-and-fusion.vue | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 71d3f06192..a3be442d21 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only :moveClass="$style.transition_stock_move" >
- +
@@ -65,10 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only :moveClass="$style.transition_picked_move" mode="out-in" > - +
@@ -369,8 +369,8 @@ const PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高い let viewScaleX = 1; let viewScaleY = 1; -const currentPick = shallowRef<{ id: string; fruit: Mono } | null>(null); -const stock = shallowRef<{ id: string; fruit: Mono }[]>([]); +const currentPick = shallowRef<{ id: string; mono: Mono } | null>(null); +const stock = shallowRef<{ id: string; mono: Mono }[]>([]); const score = ref(0); const combo = ref(0); const comboPrev = ref(0); @@ -383,7 +383,7 @@ const highScore = ref(null); class Game extends EventEmitter<{ changeScore: (score: number) => void; changeCombo: (combo: number) => void; - changeStock: (stock: { id: string; fruit: Mono }[]) => void; + changeStock: (stock: { id: string; mono: Mono }[]) => void; dropped: () => void; fusioned: (x: number, y: number, score: number) => void; gameOver: () => void; @@ -409,7 +409,7 @@ class Game extends EventEmitter<{ private latestDroppedAt = 0; private latestFusionedAt = 0; - private stock: { id: string; fruit: Mono }[] = []; + private stock: { id: string; mono: Mono }[] = []; private _combo = 0; private get combo() { @@ -509,11 +509,11 @@ class Game extends EventEmitter<{ }); } - private createBody(fruit: Mono, x: number, y: number) { + private createBody(mono: Mono, x: number, y: number) { const options: Matter.IBodyDefinition = { - label: fruit.id, + label: mono.id, //density: 0.0005, - density: fruit.size / 1000, + density: mono.size / 1000, restitution: 0.2, frictionAir: 0.01, friction: 0.7, @@ -522,16 +522,16 @@ class Game extends EventEmitter<{ //mass: 0, render: { sprite: { - texture: fruit.img, - xScale: (fruit.size / fruit.imgSize) * fruit.spriteScale, - yScale: (fruit.size / fruit.imgSize) * fruit.spriteScale, + texture: mono.img, + xScale: (mono.size / mono.imgSize) * mono.spriteScale, + yScale: (mono.size / mono.imgSize) * mono.spriteScale, }, }, }; - if (fruit.shape === 'circle') { - return Matter.Bodies.circle(x, y, fruit.size / 2, options); - } else if (fruit.shape === 'rectangle') { - return Matter.Bodies.rectangle(x, y, fruit.size, fruit.size, options); + if (mono.shape === 'circle') { + return Matter.Bodies.circle(x, y, mono.size / 2, options); + } else if (mono.shape === 'rectangle') { + return Matter.Bodies.rectangle(x, y, mono.size, mono.size, options); } else { throw new Error('unrecognized shape'); } @@ -553,11 +553,11 @@ class Game extends EventEmitter<{ Matter.Composite.remove(this.engine.world, [bodyA, bodyB]); this.activeBodyIds = this.activeBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id); - const currentFruit = this.monoDefinitions.find(y => y.id === bodyA.label)!; - const nextFruit = this.monoDefinitions.find(x => x.level === currentFruit.level + 1); + const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!; + const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1); - if (nextFruit) { - const body = this.createBody(nextFruit, newX, newY); + if (nextMono) { + const body = this.createBody(nextMono, newX, newY); Matter.Composite.add(this.engine.world, body); // 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする @@ -566,11 +566,11 @@ class Game extends EventEmitter<{ }, 100); const comboBonus = 1 + ((this.combo - 1) / 5); - const additionalScore = Math.round(currentFruit.score * comboBonus); + const additionalScore = Math.round(currentMono.score * comboBonus); this.score += additionalScore; const pan = ((newX / GAME_WIDTH) - 0.5) * 2; - sound.playRaw('syuilo/bubble2', 1, pan, nextFruit.sfxPitch); + sound.playRaw('syuilo/bubble2', 1, pan, nextMono.sfxPitch); this.emit('fusioned', newX, newY, additionalScore); } else { @@ -597,7 +597,7 @@ class Game extends EventEmitter<{ for (let i = 0; i < this.STOCK_MAX; i++) { this.stock.push({ id: Math.random().toString(), - fruit: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); } this.emit('changeStock', this.stock); @@ -658,12 +658,12 @@ class Game extends EventEmitter<{ const st = this.stock.shift()!; this.stock.push({ id: Math.random().toString(), - fruit: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], + mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)], }); this.emit('changeStock', this.stock); - const x = Math.min(GAME_WIDTH - this.PLAYAREA_MARGIN - (st.fruit.size / 2), Math.max(this.PLAYAREA_MARGIN + (st.fruit.size / 2), _x)); - const body = this.createBody(st.fruit, x, 50 + st.fruit.size / 2); + const x = Math.min(GAME_WIDTH - this.PLAYAREA_MARGIN - (st.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (st.mono.size / 2), _x)); + const body = this.createBody(st.mono, x, 50 + st.mono.size / 2); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); this.latestDroppedBodyId = body.id; @@ -970,7 +970,7 @@ definePageMetadata({ user-select: none; } -.currentFruit { +.currentMono { position: absolute; margin-top: 80px; z-index: 2; @@ -991,11 +991,11 @@ definePageMetadata({ user-select: none; } -.currentFruitArrow { +.currentMonoArrow { position: absolute; margin-top: 100px; z-index: 3; - animation: currentFruitArrow 2s ease infinite; + animation: currentMonoArrow 2s ease infinite; pointer-events: none; user-select: none; } @@ -1030,7 +1030,7 @@ definePageMetadata({ } } -@keyframes currentFruitArrow { +@keyframes currentMonoArrow { 0% { transform: translateY(0); } 25% { transform: translateY(-8px); } 50% { transform: translateY(0); } From 5e71418d5caca1cea333ee1b8629987cc69c4fbc Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sun, 7 Jan 2024 08:02:53 +0100 Subject: [PATCH 045/334] fix(frontend/emoji) restore U+FE0F for simple emojis (#12866) * fix(frontend/emoji) restore U+FE0F for simple emojis * Update CHANGELOG.md --------- Co-authored-by: syuilo --- CHANGELOG.md | 1 + .../src/components/global/MkEmoji.vue | 10 ++--- packages/frontend/src/scripts/emojilist.ts | 7 +++- packages/frontend/test/emoji.test.ts | 41 +++++++++++++++++++ packages/frontend/test/init.ts | 24 ++++++----- 5 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 packages/frontend/test/emoji.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2fb4ccd5..474fcad674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Client - Feat: 新しいゲームを追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように +- Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Enhance: チャンネルノートのピン留めをノートのメニューからできるよ diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue index 76ca8688d1..f6b21343b6 100644 --- a/packages/frontend/src/components/global/MkEmoji.vue +++ b/packages/frontend/src/components/global/MkEmoji.vue @@ -5,15 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only + + From 7e52ea4818029cbb7a981cb58a7eca0bf6b7e0e7 Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:44:13 +0900 Subject: [PATCH 067/334] Update CHANGELOG.md (#12953) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b56ff9fc9..6963d45f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 +- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正 ### Server - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました From f5b864df7bedc3b4a7abdfb09a3df9c2db8c3627 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 07:26:16 +0900 Subject: [PATCH 068/334] fix(frontend): fix game replay --- packages/frontend/src/scripts/drop-and-fusion-engine.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 9db93d1534..16fe87d97a 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -500,12 +500,13 @@ export class DropAndFusionGame extends EventEmitter<{ }); this.emit('changeStock', this.stock); - const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), Math.round(_x))); + const inputX = Math.round(_x); + const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), inputX)); const body = this.createBody(head.mono, x, 50 + head.mono.size / 2); this.logs.push({ frame: this.frame, operation: 'drop', - x, + x: inputX, }); Matter.Composite.add(this.engine.world, body); this.activeBodyIds.push(body.id); From 6bae440f3912f882fea1e3901aad2d18f2b6a3b8 Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Wed, 10 Jan 2024 09:47:47 +0900 Subject: [PATCH 069/334] bump aiscript version to 0.17.0 (#12955) * bump aiscript version to 0.17.0 * Update CHANGELOG.md --- CHANGELOG.md | 2 ++ packages/frontend/package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6963d45f63..244fd724a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように - Enhance: チャンネルノートのピン留めをノートのメニューからできるように - Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように +- Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md) + - 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意 - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 895aa47419..8c3ce30668 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,7 +24,7 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/pluginutils": "5.1.0", - "@syuilo/aiscript": "0.16.0", + "@syuilo/aiscript": "0.17.0", "@tabler/icons-webfont": "2.44.0", "@twemoji/parser": "15.0.0", "@vitejs/plugin-vue": "5.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d98224822..400051bce7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -686,8 +686,8 @@ importers: specifier: 5.1.0 version: 5.1.0(rollup@4.9.1) '@syuilo/aiscript': - specifier: 0.16.0 - version: 0.16.0 + specifier: 0.17.0 + version: 0.17.0 '@tabler/icons-webfont': specifier: 2.44.0 version: 2.44.0 @@ -7649,8 +7649,8 @@ packages: dev: false optional: true - /@syuilo/aiscript@0.16.0: - resolution: {integrity: sha512-CXvoWOq6kmOSUQtKv0IEf7Ebfkk5PO1LxAgLqgRRPgssPvDvINCXu/gFNXKdapkFMkmX+Gj8qjemKR1vnUS4ZA==} + /@syuilo/aiscript@0.17.0: + resolution: {integrity: sha512-3JtQ1rWJHMxQ3153zLCXMUOwrOgjPPYGBl0dPHhR0ohm4tn7okMQRugxMCT0t3YxByemb9FfiM6TUjd0tEGxdA==} dependencies: seedrandom: 3.0.5 stringz: 2.1.0 From 138a248a6ce875af812c8ab126b78817d495b0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Wed, 10 Jan 2024 10:40:09 +0900 Subject: [PATCH 070/334] =?UTF-8?q?fix(drop-and-fusion):=20=E3=83=90?= =?UTF-8?q?=E3=83=96=E3=83=AB=E3=82=B2=E3=83=BC=E3=83=A0=E3=81=AE=E3=83=AA?= =?UTF-8?q?=E3=83=88=E3=83=A9=E3=82=A4=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=A7?= =?UTF-8?q?=E3=83=AA=E3=83=88=E3=83=A9=E3=82=A4=E3=81=8C=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20(#12957)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ゲーム中なら諦める、ゲームオーバー画面の表示中はリスタートになるように --- packages/frontend/src/pages/drop-and-fusion.vue | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 974daf35e4..d041a675f8 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -153,7 +153,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- Retry + Surrender + Retry
@@ -483,15 +484,22 @@ async function surrender() { game.surrender(); } +async function retry() { + end(); + await start(); +} + function end() { game.dispose(); isGameOver.value = false; + replaying.value = false; currentPick.value = null; dropReady.value = true; stock.value = []; score.value = 0; combo.value = 0; comboPrev.value = 0; + maxCombo.value = 0; bgmNodes?.soundSource.stop(); gameStarted.value = false; } From 3d9e42efca8792bcfa1be7bd6125cf732db50fdb Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 11:38:49 +0900 Subject: [PATCH 071/334] =?UTF-8?q?enhance(drop-and-fusion):=20=E3=83=AA?= =?UTF-8?q?=E3=83=97=E3=83=AC=E3=82=A4=E3=81=AE=E5=80=8D=E9=80=9F=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/drop-and-fusion.vue | 12 ++- .../src/scripts/drop-and-fusion-engine.ts | 79 ++++++++++--------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index d041a675f8..f585519459 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -103,7 +103,11 @@ SPDX-License-Identifier: AGPL-3.0-only
- END REPLAY +
+ END REPLAY + x2 + x4 +
@@ -437,10 +441,15 @@ const gameStarted = ref(false); const highScore = ref(null); const showConfig = ref(false); const replaying = ref(false); +const replayPlaybackRate = ref(1); const mute = ref(false); const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); +watch(replayPlaybackRate, (newValue) => { + game.replayPlaybackRate = newValue; +}); + function onClick(ev: MouseEvent) { if (!containerElRect) return; if (replaying.value) return; @@ -493,6 +502,7 @@ function end() { game.dispose(); isGameOver.value = false; replaying.value = false; + replayPlaybackRate.value = 1; currentPick.value = null; dropReady.value = true; stock.value = []; diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 16fe87d97a..a59eb271ec 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -44,7 +44,7 @@ export class DropAndFusionGame extends EventEmitter<{ gameOver: () => void; }> { private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる - private COMBO_INTERVAL = 1000; + private COMBO_INTERVAL = 60; // frame public readonly DROP_INTERVAL = 500; public readonly PLAYAREA_MARGIN = 25; private STOCK_MAX = 4; @@ -76,7 +76,7 @@ export class DropAndFusionGame extends EventEmitter<{ private latestDroppedBodyId: Matter.Body['id'] | null = null; private latestDroppedAt = 0; - private latestFusionedAt = 0; + private latestFusionedAt = 0; // frame private stock: { id: string; mono: Mono }[] = []; private holding: { id: string; mono: Mono } | null = null; @@ -100,6 +100,8 @@ export class DropAndFusionGame extends EventEmitter<{ private comboIntervalId: number | null = null; + public replayPlaybackRate = 1; + constructor(opts: { canvas: HTMLCanvasElement; width: number; @@ -219,13 +221,12 @@ export class DropAndFusionGame extends EventEmitter<{ } private fusion(bodyA: Matter.Body, bodyB: Matter.Body) { - const now = Date.now(); - if (this.latestFusionedAt > now - this.COMBO_INTERVAL) { + if (this.latestFusionedAt > this.frame - this.COMBO_INTERVAL) { this.combo++; } else { this.combo = 1; } - this.latestFusionedAt = now; + this.latestFusionedAt = this.frame; // TODO: 単に位置だけでなくそれぞれの動きベクトルも融合する? const newX = (bodyA.position.x + bodyB.position.x) / 2; @@ -390,44 +391,43 @@ export class DropAndFusionGame extends EventEmitter<{ } }); - this.comboIntervalId = window.setInterval(() => { - if (this.latestFusionedAt < Date.now() - this.COMBO_INTERVAL) { - this.combo = 0; - } - }, 500); - if (logs) { const playTick = () => { - this.frame++; - const log = logs.find(x => x.frame === this.frame - 1); - if (log) { - switch (log.operation) { - case 'drop': { - this.drop(log.x); - break; - } - case 'hold': { - this.hold(); - break; - } - case 'surrender': { - this.surrender(); - break; - } - default: - break; + for (let i = 0; i < this.replayPlaybackRate; i++) { + this.frame++; + if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) { + this.combo = 0; } - } - this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { - if (x.frame === this.frame) { - x.callback(); - return false; - } else { - return true; + const log = logs.find(x => x.frame === this.frame - 1); + if (log) { + switch (log.operation) { + case 'drop': { + this.drop(log.x); + break; + } + case 'hold': { + this.hold(); + break; + } + case 'surrender': { + this.surrender(); + break; + } + default: + break; + } } - }); + this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { + if (x.frame === this.frame) { + x.callback(); + return false; + } else { + return true; + } + }); - Matter.Engine.update(this.engine, this.TICK_DELTA); + Matter.Engine.update(this.engine, this.TICK_DELTA); + } if (!this.isGameOver) { this.tickRaf = window.requestAnimationFrame(playTick); @@ -446,6 +446,9 @@ export class DropAndFusionGame extends EventEmitter<{ private tick() { this.frame++; + if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) { + this.combo = 0; + } this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { if (x.frame === this.frame) { x.callback(); From 4bd9f664d7213e9d6d507ae1b8cb67e2b78e766a Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 13:44:00 +0900 Subject: [PATCH 072/334] enhance(drop-and-fusion): some tweaks --- .../frontend/src/pages/drop-and-fusion.vue | 1 + .../src/scripts/drop-and-fusion-engine.ts | 29 +++++++++++++------ packages/frontend/src/scripts/sound.ts | 2 -- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index f585519459..c5ab7a33f5 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -1028,6 +1028,7 @@ definePageMetadata({ bottom: 10px; padding: 6px 8px; color: #f00; + font-weight: bold; background: #0008; border-radius: 6px; pointer-events: none; diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index a59eb271ec..342e818905 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -157,6 +157,7 @@ export class DropAndFusionGame extends EventEmitter<{ //#region walls const WALL_OPTIONS: Matter.IChamferableBodyDefinition = { + label: '_wall_', isStatic: true, friction: 0.7, slop: 1.0, @@ -254,12 +255,14 @@ export class DropAndFusionGame extends EventEmitter<{ const additionalScore = Math.round(currentMono.score * comboBonus); this.score += additionalScore; - // TODO: 効果音再生はコンポーネント側の責務なので移動する - const pan = ((newX / this.gameWidth) - 0.5) * 2; + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? + const panV = newX - this.PLAYAREA_MARGIN; + const panW = this.gameWidth - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN; + const pan = ((panV / panW) - 0.5) * 2; sound.playUrl('/client-assets/drop-and-fusion/bubble2.mp3', { volume: this.sfxVolume, pan, - playbackRate: nextMono.sfxPitch, + playbackRate: nextMono.sfxPitch * this.replayPlaybackRate, }); this.emit('monoAdded', nextMono); @@ -293,7 +296,7 @@ export class DropAndFusionGame extends EventEmitter<{ this.tickRaf = null; this.emit('gameOver'); - // TODO: 効果音再生はコンポーネント側の責務なので移動する + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? sound.playUrl('/client-assets/drop-and-fusion/gameover.mp3', { volume: this.sfxVolume, }); @@ -377,14 +380,19 @@ export class DropAndFusionGame extends EventEmitter<{ } else { const energy = pairs.collision.depth; if (energy > minCollisionEnergyForSound) { - // TODO: 効果音再生はコンポーネント側の責務なので移動する + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? const vol = ((Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4) * this.sfxVolume; - const pan = ((((bodyA.position.x + bodyB.position.x) / 2) / this.gameWidth) - 0.5) * 2; + const panV = + pairs.bodyA.label === '_wall_' ? bodyB.position.x - this.PLAYAREA_MARGIN : + pairs.bodyB.label === '_wall_' ? bodyA.position.x - this.PLAYAREA_MARGIN : + ((bodyA.position.x + bodyB.position.x) / 2) - this.PLAYAREA_MARGIN; + const panW = this.gameWidth - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN; + const pan = ((panV / panW) - 0.5) * 2; const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10))); sound.playUrl('/client-assets/drop-and-fusion/poi1.mp3', { volume: vol, pan, - playbackRate: pitch, + playbackRate: pitch * this.replayPlaybackRate, }); } } @@ -518,11 +526,14 @@ export class DropAndFusionGame extends EventEmitter<{ this.emit('dropped'); this.emit('monoAdded', head.mono); - // TODO: 効果音再生はコンポーネント側の責務なので移動する - const pan = ((x / this.gameWidth) - 0.5) * 2; + // TODO: 効果音再生はコンポーネント側の責務なので移動するべき? + const panV = x - this.PLAYAREA_MARGIN; + const panW = this.gameWidth - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN; + const pan = ((panV / panW) - 0.5) * 2; sound.playUrl('/client-assets/drop-and-fusion/poi2.mp3', { volume: this.sfxVolume, pan, + playbackRate: this.replayPlaybackRate, }); } diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 142ddf87c9..05c8977ecf 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -99,7 +99,6 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; }) } if (options?.useCache ?? true) { if (cache.has(url)) { - if (_DEV_) console.log('use cache'); return cache.get(url) as AudioBuffer; } } @@ -128,7 +127,6 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; }) */ export function playMisskeySfx(operationType: OperationType) { const sound = defaultStore.state[`sound_${operationType}`]; - if (_DEV_) console.log('play', operationType, sound); if (sound.type == null || !canPlay) return; canPlay = false; From c1c363bf08a391400e4b8b1df91962c26f2f3192 Mon Sep 17 00:00:00 2001 From: 1Step621 <86859447+1STEP621@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:06:04 +0900 Subject: [PATCH 073/334] =?UTF-8?q?Enhance(frontend):=20=E7=B5=B5=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=83=94=E3=83=83=E3=82=AB=E3=83=BC/=E3=82=AA?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=82=B3=E3=83=B3=E3=83=97=E3=83=AA=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=A7=E5=AE=8C=E5=85=A8=E4=B8=80=E8=87=B4=E3=81=AE?= =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E3=82=92=E5=84=AA=E5=85=88=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#12928)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 絵文字ピッカー/オートコンプリートで完全一致の絵文字を優先するように * update CHANGELOG.md * improve performance --- CHANGELOG.md | 1 + .../frontend/src/components/MkAutocomplete.vue | 17 +++++++++++++---- .../frontend/src/components/MkEmojiPicker.vue | 13 +++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244fd724a9..13ad3a3508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように - Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md) - 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意 +- Enhance: 絵文字ピッカー・オートコンプリートで、完全一致した絵文字を優先的に表示するように - Fix: ネイティブモードの絵文字がモノクロにならないように - Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 - Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正 diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 49884c705f..15eda4499f 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -262,15 +262,24 @@ function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): } const matched = new Map(); - - // 前方一致(エイリアスなし) + // 完全一致(エイリアス込み) emojiDb.some(x => { - if (x.name.startsWith(query) && !x.aliasOf) { - matched.set(x.name, { emoji: x, score: query.length + 1 }); + if (x.name === query && !matched.has(x.aliasOf ?? x.name)) { + matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length + 2 }); } return matched.size === max; }); + // 前方一致(エイリアスなし) + if (matched.size < max) { + emojiDb.some(x => { + if (x.name.startsWith(query) && !x.aliasOf) { + matched.set(x.name, { emoji: x, score: query.length + 1 }); + } + return matched.size === max; + }); + } + // 前方一致(エイリアス込み) if (matched.size < max) { emojiDb.some(x => { diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index f36d46506f..84424c58ed 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -221,6 +221,19 @@ watch(q, () => { } } } else { + if (customEmojisMap.has(newQ)) { + matches.add(customEmojisMap.get(newQ)!); + } + if (matches.size >= max) return matches; + + for (const emoji of emojis) { + if (emoji.aliases.some(alias => alias === newQ)) { + matches.add(emoji); + if (matches.size >= max) break; + } + } + if (matches.size >= max) return matches; + for (const emoji of emojis) { if (emoji.name.startsWith(newQ)) { matches.add(emoji); From 5c786cace839147a11fadac6ee46da29db5f2457 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 17:31:59 +0900 Subject: [PATCH 074/334] enhance(drop-and-fusion): add game description --- locales/index.d.ts | 8 ++++++++ locales/ja-JP.yml | 7 +++++++ packages/frontend/src/pages/drop-and-fusion.vue | 16 +++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index aa74ba54b0..852cbdd27d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1199,6 +1199,14 @@ export interface Locale { "showReplay": string; "replay": string; "replaying": string; + "_bubbleGame": { + "howToPlay": string; + "_howToPlay": { + "section1": string; + "section2": string; + "section3": string; + }; + }; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4863bbe770..f85dc0fcf8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1197,6 +1197,13 @@ showReplay: "リプレイを見る" replay: "リプレイ" replaying: "リプレイ中" +_bubbleGame: + howToPlay: "遊び方" + _howToPlay: + section1: "位置を調整してハコにモノを落とします。" + section2: "同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。" + section3: "モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう!" + _announcement: forExistingUsers: "既存ユーザーのみ" forExistingUsersDescription: "有効にすると、このお知らせ作成時点で存在するユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。" diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index c5ab7a33f5..9fb7ab2e23 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -8,13 +8,13 @@ SPDX-License-Identifier: AGPL-3.0-only
-
-
+
+
-
+
@@ -33,6 +33,16 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
{{ i18n.ts._bubbleGame.howToPlay }}
+
    +
  1. {{ i18n.ts._bubbleGame._howToPlay.section1 }}
  2. +
  3. {{ i18n.ts._bubbleGame._howToPlay.section2 }}
  4. +
  5. {{ i18n.ts._bubbleGame._howToPlay.section3 }}
  6. +
+
+
From 36fd7d17cf1c71fa59eae445d05498a7bf5ab173 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Jan 2024 19:54:59 +0900 Subject: [PATCH 075/334] enhance(drop-and-fusion): some tweaks --- .../src/pages/drop-and-fusion.game.vue | 1052 +++++++++++++++++ .../frontend/src/pages/drop-and-fusion.vue | 982 +-------------- .../src/scripts/drop-and-fusion-engine.ts | 2 +- 3 files changed, 1088 insertions(+), 948 deletions(-) create mode 100644 packages/frontend/src/pages/drop-and-fusion.game.vue diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue new file mode 100644 index 0000000000..acaebbadf7 --- /dev/null +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -0,0 +1,1052 @@ + + + + + + + diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index 9fb7ab2e23..7bd0eef000 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -4,10 +4,16 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 342e818905..d64c6015a5 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -33,6 +33,7 @@ type Log = { operation: 'surrender'; }; +// TODO: インスタンスを作り直さなくてもゲームをリスタートできるようにする export class DropAndFusionGame extends EventEmitter<{ changeScore: (newScore: number) => void; changeCombo: (newCombo: number) => void; @@ -307,7 +308,6 @@ export class DropAndFusionGame extends EventEmitter<{ async function loadSingleMonoTexture(mono: Mono, game: DropAndFusionGame) { // Matter-js内にキャッシュがある場合はスキップ if (game.render.textures[mono.img]) return; - console.log('loading', mono.img); let src = mono.img; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition From 762fa6a8d85691e2d5d94a46b23d7641feefd402 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 11 Jan 2024 12:34:03 +0900 Subject: [PATCH 076/334] enhance(drop-and-fusion): make game engine headless for server-side running --- .../src/pages/drop-and-fusion.game.vue | 458 ++++++++++++------ .../src/scripts/drop-and-fusion-engine.ts | 359 ++++---------- 2 files changed, 397 insertions(+), 420 deletions(-) diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index acaebbadf7..3fefb49fae 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
HOLD - +
- +
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only :moveClass="$style.transition_picked_move" mode="out-in" > - + diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index 9930b321f7..b970ff1df4 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -27,11 +27,6 @@ function toolsMenuItems(): MenuItem[] { to: '/clicker', text: '🍪👈', icon: 'ti ti-cookie', - }, { - type: 'link', - to: '/bubble-game', - text: i18n.ts.bubbleGame, - icon: 'ti ti-apple', }, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? { type: 'link', to: '/custom-emojis-manager', From 8b0fdfcd69334dbf934a69cf707826b3be8cf2d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:17:01 +0900 Subject: [PATCH 107/334] =?UTF-8?q?enhance:=20=E5=8B=95=E7=94=BB=E3=83=BB?= =?UTF-8?q?=E9=9F=B3=E5=A3=B0=E5=91=A8=E3=82=8A=E3=81=AEUI=E3=81=A8?= =?UTF-8?q?=E5=8B=95=E4=BD=9C=E6=94=B9=E8=89=AF=20(#12925)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * (fix) `/files` をバイトレンジリクエストに対応させる * video * audio * fix * fix * spdx * fix (rangeRequest) * fix * Update CHANGELOG.md * (add) ボリュームを保存できるように * (fix) ミュート復帰時に音量が固定される * named export * tweak design * Add sensitive class for audio component * Refactor seekbar styles * Refactor hms * Revert "(add) ボリュームを保存できるように" This reverts commit 6271f9493b63f96d0dd9915207e97fe120ef9037. * Revert "(fix) ミュート復帰時に音量が固定される" This reverts commit a65002b56ecdcb10f76bcc2debbe38593a69643f. * revert revert changes --------- Co-authored-by: syuilo --- CHANGELOG.md | 2 + locales/index.d.ts | 2 + locales/ja-JP.yml | 2 + .../backend/src/server/FileServerService.ts | 111 +++- .../frontend/src/components/MkMediaAudio.vue | 363 ++++++++++++ .../frontend/src/components/MkMediaBanner.vue | 13 +- .../frontend/src/components/MkMediaRange.vue | 150 +++++ .../frontend/src/components/MkMediaVideo.vue | 540 ++++++++++++++++-- packages/frontend/src/filters/hms.ts | 65 +++ packages/frontend/src/scripts/device-kind.ts | 7 + 10 files changed, 1180 insertions(+), 75 deletions(-) create mode 100644 packages/frontend/src/components/MkMediaAudio.vue create mode 100644 packages/frontend/src/components/MkMediaRange.vue create mode 100644 packages/frontend/src/filters/hms.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1fdcf9ee..945b6ac1ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ ### Client - Feat: 新しいゲームを追加 +- Feat: 音声・映像プレイヤーを追加 - Feat: 絵文字の詳細ダイアログを追加 - Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように @@ -38,6 +39,7 @@ - Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました - Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916) - Enhance: クリップをエクスポートできるように +- Enhance: `/files`のファイルに対してHTTP Rangeリクエストを行えるように - Enhance: `api.json`のOpenAPI Specificationを3.1.0に更新 - Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正 - Fix: `notes/create`で、`text`が空白文字のみで構成されているか`null`であって、かつ`text`だけであるリクエストに対するレスポンスが400になるように変更 diff --git a/locales/index.d.ts b/locales/index.d.ts index dafbdd3559..71134544d9 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1061,6 +1061,8 @@ export interface Locale { "noteIdOrUrl": string; "video": string; "videos": string; + "audio": string; + "audioFiles": string; "dataSaver": string; "accountMigration": string; "accountMoved": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 58952894b3..743a3ca38e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1058,6 +1058,8 @@ limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小し noteIdOrUrl: "ノートIDまたはURL" video: "動画" videos: "動画" +audio: "音声" +audioFiles: "音声" dataSaver: "データセーバー" accountMigration: "アカウントの移行" accountMoved: "このユーザーは新しいアカウントに移行しました:" diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index f59996ce17..7745a6cb78 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -168,11 +168,35 @@ export class FileServerService { } if (!image) { - image = { - data: fs.createReadStream(file.path), - ext: file.ext, - type: file.mime, - }; + if (request.headers.range && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + + image = { + data: fs.createReadStream(file.path, { + start, + end, + }), + ext: file.ext, + type: file.mime, + }; + + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + } else { + image = { + data: fs.createReadStream(file.path), + ext: file.ext, + type: file.mime, + }; + } } if ('pipe' in image.data && typeof image.data.pipe === 'function') { @@ -203,11 +227,54 @@ export class FileServerService { reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream'); reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Content-Disposition', contentDisposition('inline', filename)); + + if (request.headers.range && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + const fileStream = fs.createReadStream(file.path, { + start, + end, + }); + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + reply.code(206); + return fileStream; + } + return fs.createReadStream(file.path); } else { reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream'); reply.header('Cache-Control', 'max-age=31536000, immutable'); reply.header('Content-Disposition', contentDisposition('inline', file.filename)); + + if (request.headers.range && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + console.log(end); + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + const fileStream = fs.createReadStream(file.path, { + start, + end, + }); + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + reply.code(206); + return fileStream; + } + return fs.createReadStream(file.path); } } catch (e) { @@ -340,11 +407,35 @@ export class FileServerService { } if (!image) { - image = { - data: fs.createReadStream(file.path), - ext: file.ext, - type: file.mime, - }; + if (request.headers.range && file.file && file.file.size > 0) { + const range = request.headers.range as string; + const parts = range.replace(/bytes=/, '').split('-'); + const start = parseInt(parts[0], 10); + let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; + if (end > file.file.size) { + end = file.file.size - 1; + } + const chunksize = end - start + 1; + + image = { + data: fs.createReadStream(file.path, { + start, + end, + }), + ext: file.ext, + type: file.mime, + }; + + reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); + reply.header('Accept-Ranges', 'bytes'); + reply.header('Content-Length', chunksize); + } else { + image = { + data: fs.createReadStream(file.path), + ext: file.ext, + type: file.mime, + }; + } } if ('cleanup' in file) { diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue new file mode 100644 index 0000000000..75b31b9a49 --- /dev/null +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -0,0 +1,363 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 3f8fef6632..b21960a490 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -5,20 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index b7190f6335..9eab855004 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -13,6 +13,7 @@ import MkMention from '@/components/MkMention.vue'; import MkEmoji from '@/components/global/MkEmoji.vue'; import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; import MkCode from '@/components/MkCode.vue'; +import MkCodeInline from '@/components/MkCodeInline.vue'; import MkGoogle from '@/components/MkGoogle.vue'; import MkSparkle from '@/components/MkSparkle.vue'; import MkA from '@/components/global/MkA.vue'; @@ -373,10 +374,9 @@ export default function(props: MfmProps, context: SetupContext) { } case 'inlineCode': { - return [h(MkCode, { + return [h(MkCodeInline, { key: Math.random(), code: token.props.code, - inline: true, })]; } diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 4318694d4f..fabbc1c05d 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
From 67a41c09ae5924ec97237dc7869de579b5f74510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:45:11 +0900 Subject: [PATCH 118/334] =?UTF-8?q?fix(frontend/MediaVideo):=20=E5=86=8D?= =?UTF-8?q?=E7=94=9F=E3=82=B7=E3=83=BC=E3=82=AF=E3=83=90=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E5=BD=93=E3=81=9F=E3=82=8A=E5=88=A4=E5=AE=9A=E3=82=92=E8=AA=BF?= =?UTF-8?q?=E6=95=B4=20(#13027)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend/MediaVideo): 再生シークバーの当たり判定を調整 * fix --- packages/frontend/src/components/MkMediaAudio.vue | 6 +++--- packages/frontend/src/components/MkMediaRange.vue | 8 +++++--- packages/frontend/src/components/MkMediaVideo.vue | 7 +++++-- packages/frontend/src/filters/hms.ts | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index 75b31b9a49..53fd3b2d55 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -138,7 +138,7 @@ const rangePercent = computed({ audioEl.value.currentTime = to * durationMs.value / 1000; }, }); -const volume = ref(.5); +const volume = ref(.25); const bufferedEnd = ref(0); const bufferedDataRatio = computed(() => { if (!audioEl.value) return 0; @@ -161,7 +161,7 @@ function togglePlayPause() { function toggleMute() { if (volume.value === 0) { - volume.value = .5; + volume.value = .25; } else { volume.value = 0; } @@ -207,7 +207,7 @@ function init() { isActuallyPlaying.value = false; isPlaying.value = false; }); - + durationMs.value = audioEl.value.duration * 1000; audioEl.value.addEventListener('durationchange', () => { if (audioEl.value) { diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue index e6303a5c41..a150ae9843 100644 --- a/packages/frontend/src/components/MkMediaRange.vue +++ b/packages/frontend/src/components/MkMediaRange.vue @@ -5,9 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 977c9020c7..0a113458a1 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -176,7 +176,7 @@ const rangePercent = computed({ videoEl.value.currentTime = to * durationMs.value / 1000; }, }); -const volume = ref(.5); +const volume = ref(.25); const bufferedEnd = ref(0); const bufferedDataRatio = computed(() => { if (!videoEl.value) return 0; @@ -236,7 +236,7 @@ function toggleFullscreen() { function toggleMute() { if (volume.value === 0) { - volume.value = .5; + volume.value = .25; } else { volume.value = 0; } @@ -535,6 +535,9 @@ onDeactivated(() => { .seekbarRoot { grid-area: seekbar; + /* ▼シークバー操作をやりやすくするためにクリックイベントが伝播されないエリアを拡張する */ + margin: -10px; + padding: 10px; } @container (min-width: 500px) { diff --git a/packages/frontend/src/filters/hms.ts b/packages/frontend/src/filters/hms.ts index 7b5da965ff..73db7becc2 100644 --- a/packages/frontend/src/filters/hms.ts +++ b/packages/frontend/src/filters/hms.ts @@ -5,7 +5,7 @@ import { i18n } from '@/i18n.js'; -export function hms(ms: number, options: { +export function hms(ms: number, options?: { textFormat?: 'colon' | 'locale'; enableSeconds?: boolean; enableMs?: boolean; From c1019a006bfa48ba7398daa02788c50be0bfb35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:21:33 +0900 Subject: [PATCH 119/334] =?UTF-8?q?feat(frontend):=20=E6=A8=AA=E3=82=B9?= =?UTF-8?q?=E3=83=AF=E3=82=A4=E3=83=97=E3=81=A7=E3=82=BF=E3=83=96=E3=82=92?= =?UTF-8?q?=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E3=82=8B=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=20(#13011)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * (add) 横スワイプでタブを切り替える機能 * Change Changelog * y方向の移動が一定量を超えたらスワイプを中断するように * Update swipe distance thresholds * Remove console.log * adjust threshold * rename, use v-model * fix * Update MkHorizontalSwipe.vue Co-authored-by: syuilo * use css module --------- Co-authored-by: syuilo --- CHANGELOG.md | 1 + locales/index.d.ts | 1 + locales/ja-JP.yml | 1 + .../src/components/MkHorizontalSwipe.vue | 209 ++++++++++++++++++ packages/frontend/src/pages/about.vue | 171 +++++++------- packages/frontend/src/pages/announcements.vue | 57 ++--- packages/frontend/src/pages/channel.vue | 90 ++++---- packages/frontend/src/pages/channels.vue | 77 +++---- packages/frontend/src/pages/drive.file.vue | 15 +- packages/frontend/src/pages/explore.vue | 11 +- .../frontend/src/pages/flash/flash-index.vue | 41 ++-- packages/frontend/src/pages/gallery/index.vue | 11 +- packages/frontend/src/pages/instance-info.vue | 206 ++++++++--------- .../frontend/src/pages/my-clips/index.vue | 26 ++- packages/frontend/src/pages/notifications.vue | 28 ++- packages/frontend/src/pages/pages.vue | 47 ++-- packages/frontend/src/pages/search.vue | 25 ++- .../frontend/src/pages/settings/general.vue | 2 + packages/frontend/src/pages/timeline.vue | 46 ++-- packages/frontend/src/pages/user/index.vue | 30 +-- packages/frontend/src/store.ts | 4 + 21 files changed, 685 insertions(+), 414 deletions(-) create mode 100644 packages/frontend/src/components/MkHorizontalSwipe.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b43319614..605b8af92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Feat: 絵文字の詳細ダイアログを追加 - Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加 - デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です +- Feat: スワイプでタブを切り替えられるように - Enhance: MFM等のコードブロックに全文コピー用のボタンを追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように - Enhance: チャンネルノートのピン留めをノートのメニューからできるように diff --git a/locales/index.d.ts b/locales/index.d.ts index 71134544d9..a659e790cc 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1204,6 +1204,7 @@ export interface Locale { "ranking": string; "lastNDays": string; "backToTitle": string; + "enableHorizontalSwipe": string; "_bubbleGame": { "howToPlay": string; "_howToPlay": { diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 743a3ca38e..8749a5f49f 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1201,6 +1201,7 @@ replaying: "リプレイ中" ranking: "ランキング" lastNDays: "直近{n}日" backToTitle: "タイトルへ" +enableHorizontalSwipe: "スワイプしてタブを切り替える" _bubbleGame: howToPlay: "遊び方" diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue new file mode 100644 index 0000000000..2c62aadbf4 --- /dev/null +++ b/packages/frontend/src/components/MkHorizontalSwipe.vue @@ -0,0 +1,209 @@ + + + + + + diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue index f402b26ad8..4ba1b6da76 100644 --- a/packages/frontend/src/pages/about.vue +++ b/packages/frontend/src/pages/about.vue @@ -6,98 +6,100 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -114,6 +116,7 @@ import FormSplit from '@/components/form/split.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkInstanceStats from '@/components/MkInstanceStats.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; import number from '@/filters/number.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index 5632bf7caf..c31c6d0903 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -7,34 +7,36 @@ SPDX-License-Identifier: AGPL-3.0-only -
- {{ i18n.ts.youHaveUnreadAnnouncements }} - -
-
{{ i18n.ts.forYou }}
-
- 🆕 - - - - - - - {{ announcement.title }} -
-
- - -
- + +
+ {{ i18n.ts.youHaveUnreadAnnouncements }} + +
+
{{ i18n.ts.forYou }}
+
+ 🆕 + + + + + + + {{ announcement.title }}
-
-
- {{ i18n.ts.gotIt }} -
-
-
-
+
+ + +
+ +
+
+
+ {{ i18n.ts.gotIt }} +
+ + +
+ @@ -44,6 +46,7 @@ import { ref, computed } from 'vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 971eca8cae..4cdf2eea7d 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -7,53 +7,55 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
- - - -
-
-
-
+ +
+
+ + + +
+
+
+
+
+
{{ i18n.ts.sensitive }}
+
+
+
+
-
{{ i18n.ts.sensitive }}
-
-
- + + + +
+ +
+
+
+
+ {{ i18n.ts.thisChannelArchived }} + + + + + +
+
+ +
+
+
+
+ + + + {{ i18n.ts.search }} +
+
- - - -
- -
-
-
-
- {{ i18n.ts.thisChannelArchived }} - - - - - -
-
- -
-
-
-
- - - - {{ i18n.ts.search }} -
- -
-
+
@@ -58,6 +60,7 @@ import MkInput from '@/components/MkInput.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue index 2c1e5d20a7..6a9e907963 100644 --- a/packages/frontend/src/pages/drive.file.vue +++ b/packages/frontend/src/pages/drive.file.vue @@ -9,13 +9,15 @@ SPDX-License-Identifier: AGPL-3.0-only - - - + + + + - - - + + + + @@ -23,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref, defineAsyncComponent } from 'vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const props = defineProps<{ fileId: string; diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue index f068de8880..1b80014366 100644 --- a/packages/frontend/src/pages/explore.vue +++ b/packages/frontend/src/pages/explore.vue @@ -6,17 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -26,6 +26,7 @@ import XFeatured from './explore.featured.vue'; import XUsers from './explore.users.vue'; import XRoles from './explore.roles.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue index 7852018894..53510ea232 100644 --- a/packages/frontend/src/pages/flash/flash-index.vue +++ b/packages/frontend/src/pages/flash/flash-index.vue @@ -7,32 +7,34 @@ SPDX-License-Identifier: AGPL-3.0-only -
- -
- -
-
-
- -
-
- - + +
+
-
-
- -
- +
+
+ + +
+ +
+
- -
+
+ +
+ +
+ +
+
+
+ @@ -42,6 +44,7 @@ import { computed, ref } from 'vue'; import MkFlashPreview from '@/components/MkFlashPreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue index 0198ab9700..9749888fe9 100644 --- a/packages/frontend/src/pages/gallery/index.vue +++ b/packages/frontend/src/pages/gallery/index.vue @@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
+ +
@@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
-
+
{{ i18n.ts.postToGallery }}
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+ @@ -51,6 +51,7 @@ import { watch, ref, computed } from 'vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index c8a0eeeeaa..4211dc0d87 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -7,111 +7,113 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
- - {{ instance.name || `(${i18n.ts.unknown})` }} -
-
- - - - - - - - - - - - -
- - - - - - - -
- {{ i18n.ts.stopActivityDelivery }} - {{ i18n.ts.blockThisInstance }} - {{ i18n.ts.silenceThisInstance }} - Refresh metadata + +
+
+ + {{ instance.name || `(${i18n.ts.unknown})` }}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - host-meta - host-meta.json - nodeinfo - robots.txt - manifest.json - -
-
-
-
- - - - - - - - - - - - - +
+ + + + + + + + + + + +
-
-
{{ i18n.t('recentNHours', { n: 90 }) }}
- -
{{ i18n.t('recentNDays', { n: 90 }) }}
- + + + + + + + +
+ {{ i18n.ts.stopActivityDelivery }} + {{ i18n.ts.blockThisInstance }} + {{ i18n.ts.silenceThisInstance }} + Refresh metadata +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + host-meta + host-meta.json + nodeinfo + robots.txt + manifest.json + +
+
+
+
+ + + + + + + + + + + + + +
+
+
{{ i18n.t('recentNHours', { n: 90 }) }}
+ +
{{ i18n.t('recentNDays', { n: 90 }) }}
+ +
-
-
- - - - - -
-
- - -
+
+ + + + + +
+
+ + +
+ @@ -136,6 +138,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; import MkPagination from '@/components/MkPagination.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js'; import { dateString } from '@/filters/date.js'; @@ -144,6 +147,7 @@ const props = defineProps<{ }>(); const tab = ref('overview'); + const chartSrc = ref('instance-requests'); const meta = ref(null); const instance = ref(null); diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue index 850222708e..468e46838b 100644 --- a/packages/frontend/src/pages/my-clips/index.vue +++ b/packages/frontend/src/pages/my-clips/index.vue @@ -7,20 +7,22 @@ SPDX-License-Identifier: AGPL-3.0-only -
- {{ i18n.ts.add }} + +
+ {{ i18n.ts.add }} - - + + + + + +
+
+ - -
-
- - - -
+
+
@@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { clipsCache } from '@/cache.js'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const pagination = { endpoint: 'clips/list' as const, @@ -44,6 +47,7 @@ const pagination = { }; const tab = ref('my'); + const favorites = ref(null); const pagingComponent = shallowRef>(); diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index d57bda41b5..8913a89adb 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -7,15 +7,17 @@ SPDX-License-Identifier: AGPL-3.0-only -
- -
-
- -
-
- -
+ +
+ +
+
+ +
+
+ +
+
@@ -24,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref } from 'vue'; import XNotifications from '@/components/MkNotifications.vue'; import MkNotes from '@/components/MkNotes.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -96,3 +99,10 @@ definePageMetadata(computed(() => ({ icon: 'ti ti-bell', }))); + + diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue index 22ab9ced09..8b57b1af9f 100644 --- a/packages/frontend/src/pages/pages.vue +++ b/packages/frontend/src/pages/pages.vue @@ -7,30 +7,32 @@ SPDX-License-Identifier: AGPL-3.0-only -
- -
- -
-
-
+ +
+ +
+ +
+
+
-
- - -
- -
-
-
+
+ + +
+ +
+
+
-
- -
- -
-
-
+
+ +
+ +
+
+
+
@@ -40,6 +42,7 @@ import { computed, ref } from 'vue'; import MkPagePreview from '@/components/MkPagePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { useRouter } from '@/global/router/supplier.js'; diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue index 9d5e5697ce..b68de805cf 100644 --- a/packages/frontend/src/pages/search.vue +++ b/packages/frontend/src/pages/search.vue @@ -7,18 +7,20 @@ SPDX-License-Identifier: AGPL-3.0-only - -
- -
-
- {{ i18n.ts.notesSearchNotAvailable }} -
-
+ + +
+ +
+
+ {{ i18n.ts.notesSearchNotAvailable }} +
+
- - - + + + +
@@ -29,6 +31,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { $i } from '@/account.js'; import { instance } from '@/instance.js'; import MkInfo from '@/components/MkInfo.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const XNote = defineAsyncComponent(() => import('./search.note.vue')); const XUser = defineAsyncComponent(() => import('./search.user.vue')); diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 607aaec521..e52a5ee04f 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -155,6 +155,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.enableInfiniteScroll }} {{ i18n.ts.keepScreenOn }} {{ i18n.ts.disableStreamingTimeline }} + {{ i18n.ts.enableHorizontalSwipe }}
@@ -296,6 +297,7 @@ const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn')); const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline')); const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications')); const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect')); +const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe')); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 6fe8963f51..666a9968b2 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -7,27 +7,28 @@ SPDX-License-Identifier: AGPL-3.0-only -
- - {{ i18n.ts._timelineDescription[src] }} - - - -
-
- + +
+ + {{ i18n.ts._timelineDescription[src] }} + + +
+
+ +
-
+ @@ -38,6 +39,7 @@ import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; import MkTimeline from '@/components/MkTimeline.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkPostForm from '@/components/MkPostForm.vue'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import { scroll } from '@/scripts/scroll.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; @@ -69,7 +71,9 @@ const withRenotes = ref(true); const withReplies = ref($i ? defaultStore.state.tlWithReplies : false); const onlyFiles = ref(false); -watch(src, () => queue.value = 0); +watch(src, () => { + queue.value = 0; +}); watch(withReplies, (x) => { if ($i) defaultStore.set('tlWithReplies', x); diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index 95869e7b8c..603f1bef33 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -8,19 +8,21 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - - - - - - - - - - + + + + + + + + + + + + + + +
@@ -36,6 +38,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; const XHome = defineAsyncComponent(() => import('./home.vue')); const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue')); @@ -57,6 +60,7 @@ const props = withDefaults(defineProps<{ }); const tab = ref(props.page); + const user = ref(null); const error = ref(null); diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index e3a85377d8..21b796caa1 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -427,6 +427,10 @@ export const defaultStore = markRaw(new Storage('base', { sfxVolume: 1, }, }, + enableHorizontalSwipe: { + where: 'device', + default: true, + }, sound_masterVolume: { where: 'device', From 43401210c3691ffd8f8ac78b67bd5898e7981335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Fri, 19 Jan 2024 07:58:07 +0900 Subject: [PATCH 120/334] refactor: fully typed locales (#13033) * refactor: fully typed locales * refactor: hide parameterized locale strings from type data in ts access * refactor: missing assertions * docs: annotation --- locales/generateDTS.js | 201 ++++++++++++++++++---- locales/index.d.ts | 229 +++++++++++++------------- packages/frontend/src/i18n.ts | 5 +- packages/frontend/src/scripts/i18n.ts | 113 +++++++++++-- 4 files changed, 383 insertions(+), 165 deletions(-) diff --git a/locales/generateDTS.js b/locales/generateDTS.js index d3afdd6e15..6eb5bd630d 100644 --- a/locales/generateDTS.js +++ b/locales/generateDTS.js @@ -6,54 +6,171 @@ import ts from 'typescript'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); +const parameterRegExp = /\{(\w+)\}/g; + +function createMemberType(item) { + if (typeof item !== 'string') { + return ts.factory.createTypeLiteralNode(createMembers(item)); + } + const parameters = Array.from( + item.matchAll(parameterRegExp), + ([, parameter]) => parameter, + ); + if (!parameters.length) { + return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); + } + return ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ParameterizedString'), + [ + ts.factory.createUnionTypeNode( + parameters.map((parameter) => + ts.factory.createLiteralTypeNode( + ts.factory.createStringLiteral(parameter), + ), + ), + ), + ], + ); +} function createMembers(record) { - return Object.entries(record) - .map(([k, v]) => ts.factory.createPropertySignature( + return Object.entries(record).map(([k, v]) => + ts.factory.createPropertySignature( undefined, ts.factory.createStringLiteral(k), undefined, - typeof v === 'string' - ? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) - : ts.factory.createTypeLiteralNode(createMembers(v)), - )); + createMemberType(v), + ), + ); } export default function generateDTS() { const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8')); const members = createMembers(locale); const elements = [ + ts.factory.createVariableStatement( + [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], + ts.factory.createVariableDeclarationList( + [ + ts.factory.createVariableDeclaration( + ts.factory.createIdentifier('kParameters'), + undefined, + ts.factory.createTypeOperatorNode( + ts.SyntaxKind.UniqueKeyword, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword), + ), + undefined, + ), + ], + ts.NodeFlags.Const, + ), + ), + ts.factory.createInterfaceDeclaration( + [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier('ParameterizedString'), + [ + ts.factory.createTypeParameterDeclaration( + undefined, + ts.factory.createIdentifier('T'), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('string'), + undefined, + ), + ), + ], + undefined, + [ + ts.factory.createPropertySignature( + undefined, + ts.factory.createComputedPropertyName( + ts.factory.createIdentifier('kParameters'), + ), + undefined, + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('T'), + undefined, + ), + ), + ], + ), + ts.factory.createInterfaceDeclaration( + [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier('ILocale'), + undefined, + undefined, + [ + ts.factory.createIndexSignature( + undefined, + [ + ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier('_'), + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + undefined, + ), + ], + ts.factory.createUnionTypeNode([ + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ParameterizedString'), + [ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)], + ), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ILocale'), + undefined, + ), + ]), + ), + ], + ), ts.factory.createInterfaceDeclaration( [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier('Locale'), undefined, - undefined, + [ + ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ + ts.factory.createExpressionWithTypeArguments( + ts.factory.createIdentifier('ILocale'), + undefined, + ), + ]), + ], members, ), ts.factory.createVariableStatement( [ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)], ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration( - ts.factory.createIdentifier('locales'), - undefined, - ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature( + [ + ts.factory.createVariableDeclaration( + ts.factory.createIdentifier('locales'), undefined, - [ts.factory.createParameterDeclaration( - undefined, - undefined, - ts.factory.createIdentifier('lang'), - undefined, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), - undefined, - )], - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('Locale'), - undefined, - ), - )]), - undefined, - )], - ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags, + ts.factory.createTypeLiteralNode([ + ts.factory.createIndexSignature( + undefined, + [ + ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier('lang'), + undefined, + ts.factory.createKeywordTypeNode( + ts.SyntaxKind.StringKeyword, + ), + undefined, + ), + ], + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('Locale'), + undefined, + ), + ), + ]), + undefined, + ), + ], + ts.NodeFlags.Const, ), ), ts.factory.createFunctionDeclaration( @@ -70,16 +187,28 @@ export default function generateDTS() { ), ts.factory.createExportDefault(ts.factory.createIdentifier('locales')), ]; - const printed = ts.createPrinter({ - newLine: ts.NewLineKind.LineFeed, - }).printList( - ts.ListFormat.MultiLine, - ts.factory.createNodeArray(elements), - ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS), - ); + const printed = ts + .createPrinter({ + newLine: ts.NewLineKind.LineFeed, + }) + .printList( + ts.ListFormat.MultiLine, + ts.factory.createNodeArray(elements), + ts.createSourceFile( + 'index.d.ts', + '', + ts.ScriptTarget.ESNext, + true, + ts.ScriptKind.TS, + ), + ); - fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */ + fs.writeFileSync( + `${__dirname}/index.d.ts`, + `/* eslint-disable */ // This file is generated by locales/generateDTS.js // Do not edit this file directly. -${printed}`, 'utf-8'); +${printed}`, + 'utf-8', + ); } diff --git a/locales/index.d.ts b/locales/index.d.ts index a659e790cc..a22cb63507 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1,12 +1,19 @@ /* eslint-disable */ // This file is generated by locales/generateDTS.js // Do not edit this file directly. -export interface Locale { +declare const kParameters: unique symbol; +export interface ParameterizedString { + [kParameters]: T; +} +export interface ILocale { + [_: string]: string | ParameterizedString | ILocale; +} +export interface Locale extends ILocale { "_lang_": string; "headlineMisskey": string; "introMisskey": string; - "poweredByMisskeyDescription": string; - "monthAndDay": string; + "poweredByMisskeyDescription": ParameterizedString<"name">; + "monthAndDay": ParameterizedString<"month" | "day">; "search": string; "notifications": string; "username": string; @@ -18,7 +25,7 @@ export interface Locale { "cancel": string; "noThankYou": string; "enterUsername": string; - "renotedBy": string; + "renotedBy": ParameterizedString<"user">; "noNotes": string; "noNotifications": string; "instance": string; @@ -78,8 +85,8 @@ export interface Locale { "export": string; "files": string; "download": string; - "driveFileDeleteConfirm": string; - "unfollowConfirm": string; + "driveFileDeleteConfirm": ParameterizedString<"name">; + "unfollowConfirm": ParameterizedString<"name">; "exportRequested": string; "importRequested": string; "lists": string; @@ -183,9 +190,9 @@ export interface Locale { "wallpaper": string; "setWallpaper": string; "removeWallpaper": string; - "searchWith": string; + "searchWith": ParameterizedString<"q">; "youHaveNoLists": string; - "followConfirm": string; + "followConfirm": ParameterizedString<"name">; "proxyAccount": string; "proxyAccountDescription": string; "host": string; @@ -208,7 +215,7 @@ export interface Locale { "software": string; "version": string; "metadata": string; - "withNFiles": string; + "withNFiles": ParameterizedString<"n">; "monitor": string; "jobQueue": string; "cpuAndMemory": string; @@ -237,7 +244,7 @@ export interface Locale { "processing": string; "preview": string; "default": string; - "defaultValueIs": string; + "defaultValueIs": ParameterizedString<"value">; "noCustomEmojis": string; "noJobs": string; "federating": string; @@ -266,8 +273,8 @@ export interface Locale { "imageUrl": string; "remove": string; "removed": string; - "removeAreYouSure": string; - "deleteAreYouSure": string; + "removeAreYouSure": ParameterizedString<"x">; + "deleteAreYouSure": ParameterizedString<"x">; "resetAreYouSure": string; "areYouSure": string; "saved": string; @@ -285,8 +292,8 @@ export interface Locale { "messageRead": string; "noMoreHistory": string; "startMessaging": string; - "nUsersRead": string; - "agreeTo": string; + "nUsersRead": ParameterizedString<"n">; + "agreeTo": ParameterizedString<"0">; "agree": string; "agreeBelow": string; "basicNotesBeforeCreateAccount": string; @@ -298,7 +305,7 @@ export interface Locale { "images": string; "image": string; "birthday": string; - "yearsOld": string; + "yearsOld": ParameterizedString<"age">; "registeredDate": string; "location": string; "theme": string; @@ -353,9 +360,9 @@ export interface Locale { "thisYear": string; "thisMonth": string; "today": string; - "dayX": string; - "monthX": string; - "yearX": string; + "dayX": ParameterizedString<"day">; + "monthX": ParameterizedString<"month">; + "yearX": ParameterizedString<"year">; "pages": string; "integration": string; "connectService": string; @@ -420,7 +427,7 @@ export interface Locale { "recentlyUpdatedUsers": string; "recentlyRegisteredUsers": string; "recentlyDiscoveredUsers": string; - "exploreUsersCount": string; + "exploreUsersCount": ParameterizedString<"count">; "exploreFediverse": string; "popularTags": string; "userList": string; @@ -437,16 +444,16 @@ export interface Locale { "moderationNote": string; "addModerationNote": string; "moderationLogs": string; - "nUsersMentioned": string; + "nUsersMentioned": ParameterizedString<"n">; "securityKeyAndPasskey": string; "securityKey": string; "lastUsed": string; - "lastUsedAt": string; + "lastUsedAt": ParameterizedString<"t">; "unregister": string; "passwordLessLogin": string; "passwordLessLoginDescription": string; "resetPassword": string; - "newPasswordIs": string; + "newPasswordIs": ParameterizedString<"password">; "reduceUiAnimation": string; "share": string; "notFound": string; @@ -466,7 +473,7 @@ export interface Locale { "enable": string; "next": string; "retype": string; - "noteOf": string; + "noteOf": ParameterizedString<"user">; "quoteAttached": string; "quoteQuestion": string; "noMessagesYet": string; @@ -486,12 +493,12 @@ export interface Locale { "strongPassword": string; "passwordMatched": string; "passwordNotMatched": string; - "signinWith": string; + "signinWith": ParameterizedString<"x">; "signinFailed": string; "or": string; "language": string; "uiLanguage": string; - "aboutX": string; + "aboutX": ParameterizedString<"x">; "emojiStyle": string; "native": string; "disableDrawer": string; @@ -509,7 +516,7 @@ export interface Locale { "regenerate": string; "fontSize": string; "mediaListWithOneImageAppearance": string; - "limitTo": string; + "limitTo": ParameterizedString<"x">; "noFollowRequests": string; "openImageInNewTab": string; "dashboard": string; @@ -587,7 +594,7 @@ export interface Locale { "deleteAllFiles": string; "deleteAllFilesConfirm": string; "removeAllFollowing": string; - "removeAllFollowingDescription": string; + "removeAllFollowingDescription": ParameterizedString<"host">; "userSuspended": string; "userSilenced": string; "yourAccountSuspendedTitle": string; @@ -658,9 +665,9 @@ export interface Locale { "wordMute": string; "hardWordMute": string; "regexpError": string; - "regexpErrorDescription": string; + "regexpErrorDescription": ParameterizedString<"tab" | "line">; "instanceMute": string; - "userSaysSomething": string; + "userSaysSomething": ParameterizedString<"name">; "makeActive": string; "display": string; "copy": string; @@ -686,7 +693,7 @@ export interface Locale { "abuseReports": string; "reportAbuse": string; "reportAbuseRenote": string; - "reportAbuseOf": string; + "reportAbuseOf": ParameterizedString<"name">; "fillAbuseReportDescription": string; "abuseReported": string; "reporter": string; @@ -701,7 +708,7 @@ export interface Locale { "defaultNavigationBehaviour": string; "editTheseSettingsMayBreakAccount": string; "instanceTicker": string; - "waitingFor": string; + "waitingFor": ParameterizedString<"x">; "random": string; "system": string; "switchUi": string; @@ -711,10 +718,10 @@ export interface Locale { "optional": string; "createNewClip": string; "unclip": string; - "confirmToUnclipAlreadyClippedNote": string; + "confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">; "public": string; "private": string; - "i18nInfo": string; + "i18nInfo": ParameterizedString<"link">; "manageAccessTokens": string; "accountInfo": string; "notesCount": string; @@ -764,9 +771,9 @@ export interface Locale { "needReloadToApply": string; "showTitlebar": string; "clearCache": string; - "onlineUsersCount": string; - "nUsers": string; - "nNotes": string; + "onlineUsersCount": ParameterizedString<"n">; + "nUsers": ParameterizedString<"n">; + "nNotes": ParameterizedString<"n">; "sendErrorReports": string; "sendErrorReportsDescription": string; "myTheme": string; @@ -798,7 +805,7 @@ export interface Locale { "publish": string; "inChannelSearch": string; "useReactionPickerForContextMenu": string; - "typingUsers": string; + "typingUsers": ParameterizedString<"users">; "jumpToSpecifiedDate": string; "showingPastTimeline": string; "clear": string; @@ -865,7 +872,7 @@ export interface Locale { "misskeyUpdated": string; "whatIsNew": string; "translate": string; - "translatedFrom": string; + "translatedFrom": ParameterizedString<"x">; "accountDeletionInProgress": string; "usernameInfo": string; "aiChanMode": string; @@ -896,11 +903,11 @@ export interface Locale { "continueThread": string; "deleteAccountConfirm": string; "incorrectPassword": string; - "voteConfirm": string; + "voteConfirm": ParameterizedString<"choice">; "hide": string; "useDrawerReactionPickerForMobile": string; - "welcomeBackWithName": string; - "clickToFinishEmailVerification": string; + "welcomeBackWithName": ParameterizedString<"name">; + "clickToFinishEmailVerification": ParameterizedString<"ok">; "overridedDeviceKind": string; "smartphone": string; "tablet": string; @@ -928,8 +935,8 @@ export interface Locale { "cropYes": string; "cropNo": string; "file": string; - "recentNHours": string; - "recentNDays": string; + "recentNHours": ParameterizedString<"n">; + "recentNDays": ParameterizedString<"n">; "noEmailServerWarning": string; "thereIsUnresolvedAbuseReportWarning": string; "recommended": string; @@ -938,7 +945,7 @@ export interface Locale { "driveCapOverrideCaption": string; "requireAdminForView": string; "isSystemAccount": string; - "typeToConfirm": string; + "typeToConfirm": ParameterizedString<"x">; "deleteAccount": string; "document": string; "numberOfPageCache": string; @@ -992,7 +999,7 @@ export interface Locale { "neverShow": string; "remindMeLater": string; "didYouLikeMisskey": string; - "pleaseDonate": string; + "pleaseDonate": ParameterizedString<"host">; "roles": string; "role": string; "noRole": string; @@ -1090,7 +1097,7 @@ export interface Locale { "preservedUsernamesDescription": string; "createNoteFromTheFile": string; "archive": string; - "channelArchiveConfirmTitle": string; + "channelArchiveConfirmTitle": ParameterizedString<"name">; "channelArchiveConfirmDescription": string; "thisChannelArchived": string; "displayOfNote": string; @@ -1120,8 +1127,8 @@ export interface Locale { "createCount": string; "inviteCodeCreated": string; "inviteLimitExceeded": string; - "createLimitRemaining": string; - "inviteLimitResetCycle": string; + "createLimitRemaining": ParameterizedString<"limit">; + "inviteLimitResetCycle": ParameterizedString<"time" | "limit">; "expirationDate": string; "noExpirationDate": string; "inviteCodeUsedAt": string; @@ -1134,7 +1141,7 @@ export interface Locale { "expired": string; "doYouAgree": string; "beSureToReadThisAsItIsImportant": string; - "iHaveReadXCarefullyAndAgree": string; + "iHaveReadXCarefullyAndAgree": ParameterizedString<"x">; "dialog": string; "icon": string; "forYou": string; @@ -1189,7 +1196,7 @@ export interface Locale { "doReaction": string; "code": string; "reloadRequiredToApplySettings": string; - "remainingN": string; + "remainingN": ParameterizedString<"n">; "overwriteContentConfirm": string; "seasonalScreenEffect": string; "decorate": string; @@ -1202,7 +1209,7 @@ export interface Locale { "replay": string; "replaying": string; "ranking": string; - "lastNDays": string; + "lastNDays": ParameterizedString<"n">; "backToTitle": string; "enableHorizontalSwipe": string; "_bubbleGame": { @@ -1221,7 +1228,7 @@ export interface Locale { "end": string; "tooManyActiveAnnouncementDescription": string; "readConfirmTitle": string; - "readConfirmText": string; + "readConfirmText": ParameterizedString<"title">; "shouldNotBeUsedToPresentPermanentInfo": string; "dialogAnnouncementUxWarn": string; "silence": string; @@ -1236,10 +1243,10 @@ export interface Locale { "theseSettingsCanEditLater": string; "youCanEditMoreSettingsInSettingsPageLater": string; "followUsers": string; - "pushNotificationDescription": string; + "pushNotificationDescription": ParameterizedString<"name">; "initialAccountSettingCompleted": string; - "haveFun": string; - "youCanContinueTutorial": string; + "haveFun": ParameterizedString<"name">; + "youCanContinueTutorial": ParameterizedString<"name">; "startTutorial": string; "skipAreYouSure": string; "laterAreYouSure": string; @@ -1277,7 +1284,7 @@ export interface Locale { "social": string; "global": string; "description2": string; - "description3": string; + "description3": ParameterizedString<"link">; }; "_postNote": { "title": string; @@ -1315,7 +1322,7 @@ export interface Locale { }; "_done": { "title": string; - "description": string; + "description": ParameterizedString<"link">; }; }; "_timelineDescription": { @@ -1329,10 +1336,10 @@ export interface Locale { }; "_serverSettings": { "iconUrl": string; - "appIconDescription": string; + "appIconDescription": ParameterizedString<"host">; "appIconUsageExample": string; "appIconStyleRecommendation": string; - "appIconResolutionMustBe": string; + "appIconResolutionMustBe": ParameterizedString<"resolution">; "manifestJsonOverride": string; "shortName": string; "shortNameDescription": string; @@ -1343,7 +1350,7 @@ export interface Locale { "_accountMigration": { "moveFrom": string; "moveFromSub": string; - "moveFromLabel": string; + "moveFromLabel": ParameterizedString<"n">; "moveFromDescription": string; "moveTo": string; "moveToLabel": string; @@ -1351,7 +1358,7 @@ export interface Locale { "moveAccountDescription": string; "moveAccountHowTo": string; "startMigration": string; - "migrationConfirm": string; + "migrationConfirm": ParameterizedString<"account">; "movedAndCannotBeUndone": string; "postMigrationNote": string; "movedTo": string; @@ -1793,7 +1800,7 @@ export interface Locale { "_signup": { "almostThere": string; "emailAddressInfo": string; - "emailSent": string; + "emailSent": ParameterizedString<"email">; }; "_accountDelete": { "accountDelete": string; @@ -1846,14 +1853,14 @@ export interface Locale { "save": string; "inputName": string; "cannotSave": string; - "nameAlreadyExists": string; - "applyConfirm": string; - "saveConfirm": string; - "deleteConfirm": string; - "renameConfirm": string; + "nameAlreadyExists": ParameterizedString<"name">; + "applyConfirm": ParameterizedString<"name">; + "saveConfirm": ParameterizedString<"name">; + "deleteConfirm": ParameterizedString<"name">; + "renameConfirm": ParameterizedString<"old" | "new">; "noBackups": string; - "createdAt": string; - "updatedAt": string; + "createdAt": ParameterizedString<"date" | "time">; + "updatedAt": ParameterizedString<"date" | "time">; "cannotLoad": string; "invalidFile": string; }; @@ -1898,8 +1905,8 @@ export interface Locale { "featured": string; "owned": string; "following": string; - "usersCount": string; - "notesCount": string; + "usersCount": ParameterizedString<"n">; + "notesCount": ParameterizedString<"n">; "nameAndDescription": string; "nameOnly": string; "allowRenoteToExternal": string; @@ -1927,7 +1934,7 @@ export interface Locale { "manage": string; "code": string; "description": string; - "installed": string; + "installed": ParameterizedString<"name">; "installedThemes": string; "builtinThemes": string; "alreadyInstalled": string; @@ -1950,7 +1957,7 @@ export interface Locale { "lighten": string; "inputConstantName": string; "importInfo": string; - "deleteConstantConfirm": string; + "deleteConstantConfirm": ParameterizedString<"const">; "keys": { "accent": string; "bg": string; @@ -2013,23 +2020,23 @@ export interface Locale { "_ago": { "future": string; "justNow": string; - "secondsAgo": string; - "minutesAgo": string; - "hoursAgo": string; - "daysAgo": string; - "weeksAgo": string; - "monthsAgo": string; - "yearsAgo": string; + "secondsAgo": ParameterizedString<"n">; + "minutesAgo": ParameterizedString<"n">; + "hoursAgo": ParameterizedString<"n">; + "daysAgo": ParameterizedString<"n">; + "weeksAgo": ParameterizedString<"n">; + "monthsAgo": ParameterizedString<"n">; + "yearsAgo": ParameterizedString<"n">; "invalid": string; }; "_timeIn": { - "seconds": string; - "minutes": string; - "hours": string; - "days": string; - "weeks": string; - "months": string; - "years": string; + "seconds": ParameterizedString<"n">; + "minutes": ParameterizedString<"n">; + "hours": ParameterizedString<"n">; + "days": ParameterizedString<"n">; + "weeks": ParameterizedString<"n">; + "months": ParameterizedString<"n">; + "years": ParameterizedString<"n">; }; "_time": { "second": string; @@ -2040,7 +2047,7 @@ export interface Locale { "_2fa": { "alreadyRegistered": string; "registerTOTP": string; - "step1": string; + "step1": ParameterizedString<"a" | "b">; "step2": string; "step2Click": string; "step2Uri": string; @@ -2055,7 +2062,7 @@ export interface Locale { "securityKeyName": string; "tapSecurityKey": string; "removeKey": string; - "removeKeyConfirm": string; + "removeKeyConfirm": ParameterizedString<"name">; "whyTOTPOnlyRenew": string; "renewTOTP": string; "renewTOTPConfirm": string; @@ -2156,9 +2163,9 @@ export interface Locale { }; "_auth": { "shareAccessTitle": string; - "shareAccess": string; + "shareAccess": ParameterizedString<"name">; "shareAccessAsk": string; - "permission": string; + "permission": ParameterizedString<"name">; "permissionAsk": string; "pleaseGoBack": string; "callback": string; @@ -2217,12 +2224,12 @@ export interface Locale { "_cw": { "hide": string; "show": string; - "chars": string; - "files": string; + "chars": ParameterizedString<"count">; + "files": ParameterizedString<"count">; }; "_poll": { "noOnlyOneChoice": string; - "choiceN": string; + "choiceN": ParameterizedString<"n">; "noMore": string; "canMultipleVote": string; "expiration": string; @@ -2232,16 +2239,16 @@ export interface Locale { "deadlineDate": string; "deadlineTime": string; "duration": string; - "votesCount": string; - "totalVotes": string; + "votesCount": ParameterizedString<"n">; + "totalVotes": ParameterizedString<"n">; "vote": string; "showResult": string; "voted": string; "closed": string; - "remainingDays": string; - "remainingHours": string; - "remainingMinutes": string; - "remainingSeconds": string; + "remainingDays": ParameterizedString<"d" | "h">; + "remainingHours": ParameterizedString<"h" | "m">; + "remainingMinutes": ParameterizedString<"m" | "s">; + "remainingSeconds": ParameterizedString<"s">; }; "_visibility": { "public": string; @@ -2281,7 +2288,7 @@ export interface Locale { "changeAvatar": string; "changeBanner": string; "verifiedLinkDescription": string; - "avatarDecorationMax": string; + "avatarDecorationMax": ParameterizedString<"max">; }; "_exportOrImport": { "allNotes": string; @@ -2404,16 +2411,16 @@ export interface Locale { }; "_notification": { "fileUploaded": string; - "youGotMention": string; - "youGotReply": string; - "youGotQuote": string; - "youRenoted": string; + "youGotMention": ParameterizedString<"name">; + "youGotReply": ParameterizedString<"name">; + "youGotQuote": ParameterizedString<"name">; + "youRenoted": ParameterizedString<"name">; "youWereFollowed": string; "youReceivedFollowRequest": string; "yourFollowRequestAccepted": string; "pollEnded": string; "newNote": string; - "unreadAntennaNote": string; + "unreadAntennaNote": ParameterizedString<"name">; "roleAssigned": string; "emptyPushNotificationMessage": string; "achievementEarned": string; @@ -2421,9 +2428,9 @@ export interface Locale { "checkNotificationBehavior": string; "sendTestNotification": string; "notificationWillBeDisplayedLikeThis": string; - "reactedBySomeUsers": string; - "renotedBySomeUsers": string; - "followedBySomeUsers": string; + "reactedBySomeUsers": ParameterizedString<"n">; + "renotedBySomeUsers": ParameterizedString<"n">; + "followedBySomeUsers": ParameterizedString<"n">; "_types": { "all": string; "note": string; @@ -2480,8 +2487,8 @@ export interface Locale { }; }; "_dialog": { - "charactersExceeded": string; - "charactersBelow": string; + "charactersExceeded": ParameterizedString<"current" | "max">; + "charactersBelow": ParameterizedString<"current" | "min">; }; "_disabledTimeline": { "title": string; diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts index 858db74dac..c5c4ccf820 100644 --- a/packages/frontend/src/i18n.ts +++ b/packages/frontend/src/i18n.ts @@ -10,6 +10,7 @@ import { I18n } from '@/scripts/i18n.js'; export const i18n = markRaw(new I18n(locale)); -export function updateI18n(newLocale) { - i18n.ts = newLocale; +export function updateI18n(newLocale: Locale) { + // @ts-expect-error -- private field + i18n.locale = newLocale; } diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts index 8e5f17f38a..55b5371950 100644 --- a/packages/frontend/src/scripts/i18n.ts +++ b/packages/frontend/src/scripts/i18n.ts @@ -2,33 +2,114 @@ * SPDX-FileCopyrightText: syuilo and other misskey contributors * SPDX-License-Identifier: AGPL-3.0-only */ +import type { ILocale, ParameterizedString } from '../../../../locales/index.js'; -export class I18n> { - public ts: T; +type FlattenKeys = keyof { + [K in keyof T as T[K] extends ILocale + ? FlattenKeys extends infer C extends string + ? `${K & string}.${C}` + : never + : T[K] extends TPrediction + ? K + : never]: T[K]; +}; - constructor(locale: T) { - this.ts = locale; +type ParametersOf>> = T extends ILocale + ? TKey extends `${infer K}.${infer C}` + // @ts-expect-error -- C は明らかに FlattenKeys> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。 + ? ParametersOf + : TKey extends keyof T + ? T[TKey] extends ParameterizedString + ? P + : never + : never + : never; +type Ts = { + readonly [K in keyof T as T[K] extends ParameterizedString ? never : K]: T[K] extends ILocale ? Ts : string; +}; + +export class I18n { + constructor(private locale: T) { //#region BIND this.t = this.t.bind(this); //#endregion } - // string にしているのは、ドット区切りでのパス指定を許可するため - // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも - public t(key: string, args?: Record): string { - try { - let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string; + public get ts(): Ts { + if (_DEV_) { + class Handler implements ProxyHandler { + get(target: TTarget, p: string | symbol): unknown { + const value = target[p as keyof TTarget]; - if (args) { - for (const [k, v] of Object.entries(args)) { - str = str.replace(`{${k}}`, v.toString()); + if (typeof value === 'object') { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 実際には null がくることはないので。 + return new Proxy(value!, new Handler()); + } + + if (typeof value === 'string') { + const parameters = Array.from(value.matchAll(/\{(\w+)\}/g)).map(([, parameter]) => parameter); + + if (parameters.length) { + console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`); + } + + return value; + } + + console.error(`Unexpected locale key: ${String(p)}`); + + return p; } } - return str; - } catch (err) { - console.warn(`missing localization '${key}'`); - return key; + + return new Proxy(this.locale, new Handler()) as Ts; } + + return this.locale as Ts; + } + + /** + * @deprecated なるべくこのメソッド使うよりも locale 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも + */ + public t>(key: TKey): string; + public t>>(key: TKey, args: { readonly [_ in ParametersOf]: string | number }): string; + public t(key: string, args?: { readonly [_: string]: string | number }) { + let str: string | ParameterizedString | ILocale = this.locale; + + for (const k of key.split('.')) { + str = str[k]; + + if (_DEV_) { + if (typeof str === 'undefined') { + console.error(`Unexpected locale key: ${key}`); + return key; + } + } + } + + if (args) { + if (_DEV_) { + const missing = Array.from((str as string).matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter).filter(parameter => !Object.hasOwn(args, parameter)); + + if (missing.length) { + console.error(`Missing locale parameters: ${missing.join(', ')} at ${key}`); + } + } + + for (const [k, v] of Object.entries(args)) { + const search = `{${k}}`; + + if (_DEV_) { + if (!(str as string).includes(search)) { + console.error(`Unexpected locale parameter: ${k} at ${key}`); + } + } + + str = (str as string).replace(search, v.toString()); + } + } + + return str; } } From d85085d16fb6952e742eeca38b98f9442b7ec984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Fri, 19 Jan 2024 11:54:00 +0900 Subject: [PATCH 121/334] refactor: style --- packages/frontend/src/scripts/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts index 55b5371950..3366f3eac3 100644 --- a/packages/frontend/src/scripts/i18n.ts +++ b/packages/frontend/src/scripts/i18n.ts @@ -48,7 +48,7 @@ export class I18n { } if (typeof value === 'string') { - const parameters = Array.from(value.matchAll(/\{(\w+)\}/g)).map(([, parameter]) => parameter); + const parameters = Array.from(value.matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter); if (parameters.length) { console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`); From 3ad273237557eefd75a715fe3bcb86f3d2703396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:19:06 +0900 Subject: [PATCH 122/334] =?UTF-8?q?fix(frontend/HorizontalSwipe):=20?= =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AE=E8=A6=81=E7=B4=A0=E3=81=8C?= =?UTF-8?q?=E3=81=AF=E3=81=BF=E5=87=BA=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#13036)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkHorizontalSwipe.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue index 2c62aadbf4..a7d0d5a3e4 100644 --- a/packages/frontend/src/components/MkHorizontalSwipe.vue +++ b/packages/frontend/src/components/MkHorizontalSwipe.vue @@ -180,6 +180,7 @@ watch(tabModel, (newTab, oldTab) => { diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue new file mode 100644 index 0000000000..301a177de1 --- /dev/null +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -0,0 +1,236 @@ + + + + + + + diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue new file mode 100644 index 0000000000..dbbeb20f42 --- /dev/null +++ b/packages/frontend/src/pages/reversi/game.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue new file mode 100644 index 0000000000..c483e36c24 --- /dev/null +++ b/packages/frontend/src/pages/reversi/index.vue @@ -0,0 +1,271 @@ + + + + + + + diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 98fe0043c1..8cdc7b59c6 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -103,7 +103,7 @@ export function getConfig(): UserConfig { // https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies optimizeDeps: { - include: ['misskey-js'], + include: ['misskey-js', 'misskey-reversi'], }, build: { @@ -135,7 +135,7 @@ export function getConfig(): UserConfig { // https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies commonjsOptions: { - include: [/misskey-js/, /node_modules/], + include: [/misskey-js/, /misskey-reversi/, /node_modules/], }, }, diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index f955cc5cc1..2b95e01533 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1623,6 +1623,16 @@ declare namespace entities { BubbleGameRegisterResponse, BubbleGameRankingRequest, BubbleGameRankingResponse, + ReversiCancelMatchRequest, + ReversiCancelMatchResponse, + ReversiGamesRequest, + ReversiGamesResponse, + ReversiMatchRequest, + ReversiMatchResponse, + ReversiInvitationsResponse, + ReversiShowGameRequest, + ReversiShowGameResponse, + ReversiSurrenderRequest, Error_2 as Error, UserLite, UserDetailedNotMeOnly, @@ -1659,7 +1669,9 @@ declare namespace entities { Flash, Signin, RoleLite, - Role + Role, + ReversiGameLite, + ReversiGameDetailed } } export { entities } @@ -2596,6 +2608,42 @@ type ResetPasswordRequest = operations['reset-password']['requestBody']['content // @public (undocumented) type RetentionResponse = operations['retention']['responses']['200']['content']['application/json']; +// @public (undocumented) +type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiCancelMatchResponse = operations['reversi/cancel-match']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; + +// @public (undocumented) +type ReversiGameLite = components['schemas']['ReversiGameLite']; + +// @public (undocumented) +type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json']; + +// @public (undocumented) +type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; + // @public (undocumented) type Role = components['schemas']['Role']; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index b60f449a71..e4e7d13668 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.782Z + * generatedAt: 2024-01-19T11:00:07.160Z */ import type { SwitchCaseResponseType } from '../api.js'; @@ -4007,5 +4007,71 @@ declare module '../api.js' { params: P, credential?: string | null, ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; } } diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index dc591a7046..671abd78ce 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.778Z + * generatedAt: 2024-01-19T11:00:07.158Z */ import type { @@ -544,6 +544,16 @@ import type { BubbleGameRegisterResponse, BubbleGameRankingRequest, BubbleGameRankingResponse, + ReversiCancelMatchRequest, + ReversiCancelMatchResponse, + ReversiGamesRequest, + ReversiGamesResponse, + ReversiMatchRequest, + ReversiMatchResponse, + ReversiInvitationsResponse, + ReversiShowGameRequest, + ReversiShowGameResponse, + ReversiSurrenderRequest, } from './entities.js'; export type Endpoints = { @@ -907,4 +917,10 @@ export type Endpoints = { 'retention': { req: EmptyRequest; res: RetentionResponse }; 'bubble-game/register': { req: BubbleGameRegisterRequest; res: BubbleGameRegisterResponse }; 'bubble-game/ranking': { req: BubbleGameRankingRequest; res: BubbleGameRankingResponse }; + 'reversi/cancel-match': { req: ReversiCancelMatchRequest; res: ReversiCancelMatchResponse }; + 'reversi/games': { req: ReversiGamesRequest; res: ReversiGamesResponse }; + 'reversi/match': { req: ReversiMatchRequest; res: ReversiMatchResponse }; + 'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse }; + 'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse }; + 'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse }; } diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index dfe24ce0d8..c14876c0e3 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.775Z + * generatedAt: 2024-01-19T11:00:07.156Z */ import { operations } from './types.js'; @@ -546,3 +546,13 @@ export type BubbleGameRegisterRequest = operations['bubble-game/register']['requ export type BubbleGameRegisterResponse = operations['bubble-game/register']['responses']['200']['content']['application/json']; export type BubbleGameRankingRequest = operations['bubble-game/ranking']['requestBody']['content']['application/json']; export type BubbleGameRankingResponse = operations['bubble-game/ranking']['responses']['200']['content']['application/json']; +export type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json']; +export type ReversiCancelMatchResponse = operations['reversi/cancel-match']['responses']['200']['content']['application/json']; +export type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json']; +export type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json']; +export type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json']; +export type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json']; +export type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json']; +export type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json']; +export type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json']; +export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index 5c6bebf2fd..78f14d2250 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -1,6 +1,6 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.773Z + * generatedAt: 2024-01-19T11:00:07.155Z */ import { components } from './types.js'; @@ -41,3 +41,5 @@ export type Flash = components['schemas']['Flash']; export type Signin = components['schemas']['Signin']; export type RoleLite = components['schemas']['RoleLite']; export type Role = components['schemas']['Role']; +export type ReversiGameLite = components['schemas']['ReversiGameLite']; +export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 76e2b5309c..36facf6e28 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3,7 +3,7 @@ /* * version: 2023.12.2 - * generatedAt: 2024-01-13T04:31:38.633Z + * generatedAt: 2024-01-19T11:00:07.077Z */ /** @@ -3472,6 +3472,60 @@ export type paths = { */ post: operations['bubble-game/ranking']; }; + '/reversi/cancel-match': { + /** + * reversi/cancel-match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi/cancel-match']; + }; + '/reversi/games': { + /** + * reversi/games + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['reversi/games']; + }; + '/reversi/match': { + /** + * reversi/match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi/match']; + }; + '/reversi/invitations': { + /** + * reversi/invitations + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + post: operations['reversi/invitations']; + }; + '/reversi/show-game': { + /** + * reversi/show-game + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['reversi/show-game']; + }; + '/reversi/surrender': { + /** + * reversi/surrender + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi/surrender']; + }; }; export type webhooks = Record; @@ -4404,6 +4458,72 @@ export type components = { }; usersCount: number; }); + ReversiGameLite: { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + startedAt: string | null; + isStarted: boolean; + isEnded: boolean; + form1: Record | null; + form2: Record | null; + user1Ready: boolean; + user2Ready: boolean; + /** Format: id */ + user1Id: string; + /** Format: id */ + user2Id: string; + user1: components['schemas']['User']; + user2: components['schemas']['User']; + /** Format: id */ + winnerId: string | null; + winner: components['schemas']['User'] | null; + /** Format: id */ + surrendered: string | null; + black: number | null; + bw: string; + isLlotheo: boolean; + canPutEverywhere: boolean; + loopedBoard: boolean; + }; + ReversiGameDetailed: { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + startedAt: string | null; + isStarted: boolean; + isEnded: boolean; + form1: Record | null; + form2: Record | null; + user1Ready: boolean; + user2Ready: boolean; + /** Format: id */ + user1Id: string; + /** Format: id */ + user2Id: string; + user1: components['schemas']['User']; + user2: components['schemas']['User']; + /** Format: id */ + winnerId: string | null; + winner: components['schemas']['User'] | null; + /** Format: id */ + surrendered: string | null; + black: number | null; + bw: string; + isLlotheo: boolean; + canPutEverywhere: boolean; + loopedBoard: boolean; + logs: { + at: number; + color: boolean; + pos: number; + }[]; + map: string[]; + }; }; responses: never; parameters: never; @@ -25542,5 +25662,325 @@ export type operations = { }; }; }; + /** + * reversi/cancel-match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'reversi/cancel-match': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId?: string | null; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': unknown; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/games + * @description No description provided. + * + * **Credential required**: *No* + */ + 'reversi/games': { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default false */ + my?: boolean; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['ReversiGameLite'][]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'reversi/match': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId?: string | null; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': unknown; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/invitations + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + 'reversi/invitations': { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserLite'][]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/show-game + * @description No description provided. + * + * **Credential required**: *No* + */ + 'reversi/show-game': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + gameId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['ReversiGameDetailed']; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/surrender + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'reversi/surrender': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + gameId: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; }; diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json new file mode 100644 index 0000000000..8d3ca30166 --- /dev/null +++ b/packages/misskey-reversi/package.json @@ -0,0 +1,26 @@ +{ + "name": "misskey-reversi", + "version": "0.0.1", + "main": "./built/index.js", + "types": "./built/index.d.ts", + "scripts": { + "build": "tsc", + "watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build\"", + "eslint": "eslint . --ext .js,.jsx,.ts,.tsx", + "typecheck": "tsc --noEmit", + "lint": "pnpm typecheck && pnpm eslint" + }, + "devDependencies": { + "@misskey-dev/eslint-plugin": "1.0.0", + "@types/node": "20.11.5", + "@typescript-eslint/eslint-plugin": "6.19.0", + "@typescript-eslint/parser": "6.19.0", + "eslint": "8.56.0", + "typescript": "5.3.3" + }, + "files": [ + "built" + ], + "dependencies": { + } +} diff --git a/packages/misskey-reversi/src/game.ts b/packages/misskey-reversi/src/game.ts new file mode 100644 index 0000000000..55d0b84da7 --- /dev/null +++ b/packages/misskey-reversi/src/game.ts @@ -0,0 +1,216 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * true ... 黒 + * false ... 白 + */ +export type Color = boolean; +const BLACK = true; +const WHITE = false; + +export type MapCell = 'null' | 'empty'; + +export type Options = { + isLlotheo: boolean; + canPutEverywhere: boolean; + loopedBoard: boolean; +}; + +export type Undo = { + color: Color; + pos: number; + + /** + * 反転した石の位置の配列 + */ + effects: number[]; + + turn: Color | null; +}; + +export class Game { + public map: MapCell[]; + public mapWidth: number; + public mapHeight: number; + public board: (Color | null | undefined)[]; + public turn: Color | null = BLACK; + public opts: Options; + + public prevPos = -1; + public prevColor: Color | null = null; + + private logs: Undo[] = []; + + constructor(map: string[], opts: Options) { + //#region binds + this.put = this.put.bind(this); + //#endregion + + //#region Options + this.opts = opts; + if (this.opts.isLlotheo == null) this.opts.isLlotheo = false; + if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false; + if (this.opts.loopedBoard == null) this.opts.loopedBoard = false; + //#endregion + + //#region Parse map data + this.mapWidth = map[0].length; + this.mapHeight = map.length; + const mapData = map.join(''); + + this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined); + + this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null'); + //#endregion + + // ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある + if (!this.canPutSomewhere(BLACK)) + this.turn = this.canPutSomewhere(WHITE) ? WHITE : null; + } + + public get blackCount() { + return this.board.filter(x => x === BLACK).length; + } + + public get whiteCount() { + return this.board.filter(x => x === WHITE).length; + } + + public posToXy(pos: number): number[] { + const x = pos % this.mapWidth; + const y = Math.floor(pos / this.mapWidth); + return [x, y]; + } + + public xyToPos(x: number, y: number): number { + return x + (y * this.mapWidth); + } + + public put(color: Color, pos: number) { + this.prevPos = pos; + this.prevColor = color; + + this.board[pos] = color; + + // 反転させられる石を取得 + const effects = this.effects(color, pos); + + // 反転させる + for (const pos of effects) { + this.board[pos] = color; + } + + const turn = this.turn; + + this.logs.push({ + color, + pos, + effects, + turn + }); + + this.calcTurn(); + } + + private calcTurn() { + // ターン計算 + this.turn = + this.canPutSomewhere(!this.prevColor) ? !this.prevColor : + this.canPutSomewhere(this.prevColor!) ? this.prevColor : + null; + } + + public undo() { + const undo = this.logs.pop()!; + this.prevColor = undo.color; + this.prevPos = undo.pos; + this.board[undo.pos] = null; + for (const pos of undo.effects) { + const color = this.board[pos]; + this.board[pos] = !color; + } + this.turn = undo.turn; + } + + public mapDataGet(pos: number): MapCell { + const [x, y] = this.posToXy(pos); + return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos]; + } + + public getPuttablePlaces(color: Color): number[] { + return Array.from(this.board.keys()).filter(i => this.canPut(color, i)); + } + + public canPutSomewhere(color: Color): boolean { + return this.getPuttablePlaces(color).length > 0; + } + + public canPut(color: Color, pos: number): boolean { + return ( + this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない + this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード + this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか + } + + /** + * 指定のマスに石を置いた時の、反転させられる石を取得します + * @param color 自分の色 + * @param initPos 位置 + */ + public effects(color: Color, initPos: number): number[] { + const enemyColor = !color; + + const diffVectors: [number, number][] = [ + [ 0, -1], // 上 + [+1, -1], // 右上 + [+1, 0], // 右 + [+1, +1], // 右下 + [ 0, +1], // 下 + [-1, +1], // 左下 + [-1, 0], // 左 + [-1, -1] // 左上 + ]; + + const effectsInLine = ([dx, dy]: [number, number]): number[] => { + const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy]; + + const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列 + let [x, y] = this.posToXy(initPos); + while (true) { + [x, y] = nextPos(x, y); + + // 座標が指し示す位置がボード外に出たとき + if (this.opts.loopedBoard && this.xyToPos( + (x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth), + (y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) + // 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ) + return found; + else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) + return []; // 挟めないことが確定 (盤面外に到達) + + const pos = this.xyToPos(x, y); + if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達) + const stone = this.board[pos]; + if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達) + if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見) + if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見) + } + }; + + return ([] as number[]).concat(...diffVectors.map(effectsInLine)); + } + + public get isEnded(): boolean { + return this.turn === null; + } + + public get winner(): Color | null { + return this.isEnded ? + this.blackCount == this.whiteCount ? null : + this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : + undefined as never; + } +} diff --git a/packages/misskey-reversi/src/index.ts b/packages/misskey-reversi/src/index.ts new file mode 100644 index 0000000000..20ed36f208 --- /dev/null +++ b/packages/misskey-reversi/src/index.ts @@ -0,0 +1,7 @@ +import { Game } from './game.js'; + +export { + Game, +}; + +export * as maps from './maps.js'; diff --git a/packages/misskey-reversi/src/maps.ts b/packages/misskey-reversi/src/maps.ts new file mode 100644 index 0000000000..85cf1a0485 --- /dev/null +++ b/packages/misskey-reversi/src/maps.ts @@ -0,0 +1,715 @@ +/** + * 組み込みマップ定義 + * + * データ値: + * (スペース) ... マス無し + * - ... マス + * b ... 初期配置される黒石 + * w ... 初期配置される白石 + */ + +export type Map = { + name?: string; + category?: string; + author?: string; + data: string[]; +}; + +export const fourfour: Map = { + name: '4x4', + category: '4x4', + data: [ + '----', + '-wb-', + '-bw-', + '----' + ] +}; + +export const sixsix: Map = { + name: '6x6', + category: '6x6', + data: [ + '------', + '------', + '--wb--', + '--bw--', + '------', + '------' + ] +}; + +export const roundedSixsix: Map = { + name: '6x6 rounded', + category: '6x6', + author: 'syuilo', + data: [ + ' ---- ', + '------', + '--wb--', + '--bw--', + '------', + ' ---- ' + ] +}; + +export const roundedSixsix2: Map = { + name: '6x6 rounded 2', + category: '6x6', + author: 'syuilo', + data: [ + ' -- ', + ' ---- ', + '--wb--', + '--bw--', + ' ---- ', + ' -- ' + ] +}; + +export const eighteight: Map = { + name: '8x8', + category: '8x8', + data: [ + '--------', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + '--------' + ] +}; + +export const eighteightH28: Map = { + name: '8x8 handicap 28', + category: '8x8', + data: [ + 'bbbbbbbb', + 'b------b', + 'b------b', + 'b--wb--b', + 'b--bw--b', + 'b------b', + 'b------b', + 'bbbbbbbb' + ] +}; + +export const roundedEighteight: Map = { + name: '8x8 rounded', + category: '8x8', + author: 'syuilo', + data: [ + ' ------ ', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + ' ------ ' + ] +}; + +export const roundedEighteight2: Map = { + name: '8x8 rounded 2', + category: '8x8', + author: 'syuilo', + data: [ + ' ---- ', + ' ------ ', + '--------', + '---wb---', + '---bw---', + '--------', + ' ------ ', + ' ---- ' + ] +}; + +export const roundedEighteight3: Map = { + name: '8x8 rounded 3', + category: '8x8', + author: 'syuilo', + data: [ + ' -- ', + ' ---- ', + ' ------ ', + '---wb---', + '---bw---', + ' ------ ', + ' ---- ', + ' -- ' + ] +}; + +export const eighteightWithNotch: Map = { + name: '8x8 with notch', + category: '8x8', + author: 'syuilo', + data: [ + '--- ---', + '--------', + '--------', + ' --wb-- ', + ' --bw-- ', + '--------', + '--------', + '--- ---' + ] +}; + +export const eighteightWithSomeHoles: Map = { + name: '8x8 with some holes', + category: '8x8', + author: 'syuilo', + data: [ + '--- ----', + '----- --', + '-- -----', + '---wb---', + '---bw- -', + ' -------', + '--- ----', + '--------' + ] +}; + +export const circle: Map = { + name: 'Circle', + category: '8x8', + author: 'syuilo', + data: [ + ' -- ', + ' ------ ', + ' ------ ', + '---wb---', + '---bw---', + ' ------ ', + ' ------ ', + ' -- ' + ] +}; + +export const smile: Map = { + name: 'Smile', + category: '8x8', + author: 'syuilo', + data: [ + ' ------ ', + '--------', + '-- -- --', + '---wb---', + '-- bw --', + '--- ---', + '--------', + ' ------ ' + ] +}; + +export const window: Map = { + name: 'Window', + category: '8x8', + author: 'syuilo', + data: [ + '--------', + '- -- -', + '- -- -', + '---wb---', + '---bw---', + '- -- -', + '- -- -', + '--------' + ] +}; + +export const reserved: Map = { + name: 'Reserved', + category: '8x8', + author: 'Aya', + data: [ + 'w------b', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + 'b------w' + ] +}; + +export const x: Map = { + name: 'X', + category: '8x8', + author: 'Aya', + data: [ + 'w------b', + '-w----b-', + '--w--b--', + '---wb---', + '---bw---', + '--b--w--', + '-b----w-', + 'b------w' + ] +}; + +export const parallel: Map = { + name: 'Parallel', + category: '8x8', + author: 'Aya', + data: [ + '--------', + '--------', + '--------', + '---bb---', + '---ww---', + '--------', + '--------', + '--------' + ] +}; + +export const lackOfBlack: Map = { + name: 'Lack of Black', + category: '8x8', + data: [ + '--------', + '--------', + '--------', + '---w----', + '---bw---', + '--------', + '--------', + '--------' + ] +}; + +export const squareParty: Map = { + name: 'Square Party', + category: '8x8', + author: 'syuilo', + data: [ + '--------', + '-wwwbbb-', + '-w-wb-b-', + '-wwwbbb-', + '-bbbwww-', + '-b-bw-w-', + '-bbbwww-', + '--------' + ] +}; + +export const minesweeper: Map = { + name: 'Minesweeper', + category: '8x8', + author: 'syuilo', + data: [ + 'b-b--w-w', + '-w-wb-b-', + 'w-b--w-b', + '-b-wb-w-', + '-w-bw-b-', + 'b-w--b-w', + '-b-bw-w-', + 'w-w--b-b' + ] +}; + +export const tenthtenth: Map = { + name: '10x10', + category: '10x10', + data: [ + '----------', + '----------', + '----------', + '----------', + '----wb----', + '----bw----', + '----------', + '----------', + '----------', + '----------' + ] +}; + +export const hole: Map = { + name: 'The Hole', + category: '10x10', + author: 'syuilo', + data: [ + '----------', + '----------', + '--wb--wb--', + '--bw--bw--', + '---- ----', + '---- ----', + '--wb--wb--', + '--bw--bw--', + '----------', + '----------' + ] +}; + +export const grid: Map = { + name: 'Grid', + category: '10x10', + author: 'syuilo', + data: [ + '----------', + '- - -- - -', + '----------', + '- - -- - -', + '----wb----', + '----bw----', + '- - -- - -', + '----------', + '- - -- - -', + '----------' + ] +}; + +export const cross: Map = { + name: 'Cross', + category: '10x10', + author: 'Aya', + data: [ + ' ---- ', + ' ---- ', + ' ---- ', + '----------', + '----wb----', + '----bw----', + '----------', + ' ---- ', + ' ---- ', + ' ---- ' + ] +}; + +export const charX: Map = { + name: 'Char X', + category: '10x10', + author: 'syuilo', + data: [ + '--- ---', + '---- ----', + '----------', + ' -------- ', + ' --wb-- ', + ' --bw-- ', + ' -------- ', + '----------', + '---- ----', + '--- ---' + ] +}; + +export const charY: Map = { + name: 'Char Y', + category: '10x10', + author: 'syuilo', + data: [ + '--- ---', + '---- ----', + '----------', + ' -------- ', + ' --wb-- ', + ' --bw-- ', + ' ------ ', + ' ------ ', + ' ------ ', + ' ------ ' + ] +}; + +export const walls: Map = { + name: 'Walls', + category: '10x10', + author: 'Aya', + data: [ + ' bbbbbbbb ', + 'w--------w', + 'w--------w', + 'w--------w', + 'w---wb---w', + 'w---bw---w', + 'w--------w', + 'w--------w', + 'w--------w', + ' bbbbbbbb ' + ] +}; + +export const cpu: Map = { + name: 'CPU', + category: '10x10', + author: 'syuilo', + data: [ + ' b b b b ', + 'w--------w', + ' -------- ', + 'w--------w', + ' ---wb--- ', + ' ---bw--- ', + 'w--------w', + ' -------- ', + 'w--------w', + ' b b b b ' + ] +}; + +export const checker: Map = { + name: 'Checker', + category: '10x10', + author: 'Aya', + data: [ + '----------', + '----------', + '----------', + '---wbwb---', + '---bwbw---', + '---wbwb---', + '---bwbw---', + '----------', + '----------', + '----------' + ] +}; + +export const japaneseCurry: Map = { + name: 'Japanese curry', + category: '10x10', + author: 'syuilo', + data: [ + 'w-b-b-b-b-', + '-w-b-b-b-b', + 'w-w-b-b-b-', + '-w-w-b-b-b', + 'w-w-wwb-b-', + '-w-wbb-b-b', + 'w-w-w-b-b-', + '-w-w-w-b-b', + 'w-w-w-w-b-', + '-w-w-w-w-b' + ] +}; + +export const mosaic: Map = { + name: 'Mosaic', + category: '10x10', + author: 'syuilo', + data: [ + '- - - - - ', + ' - - - - -', + '- - - - - ', + ' - w w - -', + '- - b b - ', + ' - w w - -', + '- - b b - ', + ' - - - - -', + '- - - - - ', + ' - - - - -', + ] +}; + +export const arena: Map = { + name: 'Arena', + category: '10x10', + author: 'syuilo', + data: [ + '- - -- - -', + ' - - - - ', + '- ------ -', + ' -------- ', + '- --wb-- -', + '- --bw-- -', + ' -------- ', + '- ------ -', + ' - - - - ', + '- - -- - -' + ] +}; + +export const reactor: Map = { + name: 'Reactor', + category: '10x10', + author: 'syuilo', + data: [ + '-w------b-', + 'b- - - -w', + '- --wb-- -', + '---b w---', + '- b wb w -', + '- w bw b -', + '---w b---', + '- --bw-- -', + 'w- - - -b', + '-b------w-' + ] +}; + +export const sixeight: Map = { + name: '6x8', + category: 'Special', + data: [ + '------', + '------', + '------', + '--wb--', + '--bw--', + '------', + '------', + '------' + ] +}; + +export const spark: Map = { + name: 'Spark', + category: 'Special', + author: 'syuilo', + data: [ + ' - - ', + '----------', + ' -------- ', + ' -------- ', + ' ---wb--- ', + ' ---bw--- ', + ' -------- ', + ' -------- ', + '----------', + ' - - ' + ] +}; + +export const islands: Map = { + name: 'Islands', + category: 'Special', + author: 'syuilo', + data: [ + '-------- ', + '---wb--- ', + '---bw--- ', + '-------- ', + ' - - ', + ' - - ', + ' --------', + ' --------', + ' --------', + ' --------' + ] +}; + +export const galaxy: Map = { + name: 'Galaxy', + category: 'Special', + author: 'syuilo', + data: [ + ' ------ ', + ' --www--- ', + ' ------w--- ', + '---bbb--w---', + '--b---b-w-b-', + '-b--wwb-w-b-', + '-b-w-bww--b-', + '-b-w-b---b--', + '---w--bbb---', + ' ---w------ ', + ' ---www-- ', + ' ------ ' + ] +}; + +export const triangle: Map = { + name: 'Triangle', + category: 'Special', + author: 'syuilo', + data: [ + ' -- ', + ' -- ', + ' ---- ', + ' ---- ', + ' --wb-- ', + ' --bw-- ', + ' -------- ', + ' -------- ', + '----------', + '----------' + ] +}; + +export const iphonex: Map = { + name: 'iPhone X', + category: 'Special', + author: 'syuilo', + data: [ + ' -- -- ', + '--------', + '--------', + '--------', + '--------', + '---wb---', + '---bw---', + '--------', + '--------', + '--------', + '--------', + ' ------ ' + ] +}; + +export const dealWithIt: Map = { + name: 'Deal with it!', + category: 'Special', + author: 'syuilo', + data: [ + '------------', + '--w-b-------', + ' --b-w------', + ' --w-b---- ', + ' ------- ' + ] +}; + +export const bigBoard: Map = { + name: 'Big board', + category: 'Special', + data: [ + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '-------wb-------', + '-------bw-------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------', + '----------------' + ] +}; + +export const twoBoard: Map = { + name: 'Two board', + category: 'Special', + author: 'Aya', + data: [ + '-------- --------', + '-------- --------', + '-------- --------', + '---wb--- ---wb---', + '---bw--- ---bw---', + '-------- --------', + '-------- --------', + '-------- --------' + ] +}; diff --git a/packages/misskey-reversi/tsconfig.json b/packages/misskey-reversi/tsconfig.json new file mode 100644 index 0000000000..f56b65e868 --- /dev/null +++ b/packages/misskey-reversi/tsconfig.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "target": "ES2022", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./built/", + "removeComments": true, + "strict": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "noImplicitReturns": true, + "esModuleInterop": true, + "typeRoots": [ + "./node_modules/@types" + ], + "lib": [ + "esnext", + "dom" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "test/**/*" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 825a7ab860..31394eb081 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,6 +185,9 @@ importers: content-disposition: specifier: 0.5.4 version: 0.5.4 + crc-32: + specifier: ^1.2.2 + version: 1.2.2 date-fns: specifier: 2.30.0 version: 2.30.0 @@ -263,6 +266,9 @@ importers: misskey-js: specifier: workspace:* version: link:../misskey-js + misskey-reversi: + specifier: workspace:* + version: link:../misskey-reversi ms: specifier: 3.0.0-canary.1 version: 3.0.0-canary.1 @@ -736,6 +742,9 @@ importers: compare-versions: specifier: 6.1.0 version: 6.1.0 + crc-32: + specifier: ^1.2.2 + version: 1.2.2 cropperjs: specifier: 2.0.0-beta.4 version: 2.0.0-beta.4 @@ -772,6 +781,9 @@ importers: misskey-js: specifier: workspace:* version: link:../misskey-js + misskey-reversi: + specifier: workspace:* + version: link:../misskey-reversi photoswipe: specifier: 5.4.3 version: 5.4.3 @@ -1114,6 +1126,27 @@ importers: specifier: 5.3.3 version: 5.3.3 + packages/misskey-reversi: + devDependencies: + '@misskey-dev/eslint-plugin': + specifier: 1.0.0 + version: 1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + '@types/node': + specifier: 20.11.5 + version: 20.11.5 + '@typescript-eslint/eslint-plugin': + specifier: 6.19.0 + version: 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': + specifier: 6.19.0 + version: 6.19.0(eslint@8.56.0)(typescript@5.3.3) + eslint: + specifier: 8.56.0 + version: 8.56.0 + typescript: + specifier: 5.3.3 + version: 5.3.3 + packages/sw: dependencies: esbuild: @@ -1128,7 +1161,7 @@ importers: devDependencies: '@misskey-dev/eslint-plugin': specifier: ^1.0.0 - version: 1.0.0(@typescript-eslint/eslint-plugin@6.14.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) + version: 1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) '@typescript-eslint/parser': specifier: 6.14.0 version: 6.14.0(eslint@8.56.0)(typescript@5.3.3) @@ -1812,7 +1845,7 @@ packages: '@babel/traverse': 7.22.11 '@babel/types': 7.22.17 convert-source-map: 1.9.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1835,7 +1868,7 @@ packages: '@babel/traverse': 7.23.5 '@babel/types': 7.23.5 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -1937,7 +1970,7 @@ packages: '@babel/core': 7.23.5 '@babel/helper-compilation-targets': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -3336,7 +3369,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.5 '@babel/types': 7.22.17 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3354,7 +3387,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.5 '@babel/types': 7.23.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4233,7 +4266,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.19.0 ignore: 5.2.4 @@ -4250,7 +4283,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.19.0 ignore: 5.2.4 @@ -4515,7 +4548,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -4571,7 +4604,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -4592,14 +4625,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.7.1 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.10.5) + jest-config: 29.7.0(@types/node@20.11.5) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4634,7 +4667,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-mock: 29.7.0 dev: true @@ -4661,7 +4694,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -4694,7 +4727,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.18 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -4788,7 +4821,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/yargs': 16.0.5 chalk: 4.1.2 dev: true @@ -4800,7 +4833,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/yargs': 17.0.19 chalk: 4.1.2 dev: true @@ -4992,6 +5025,34 @@ packages: eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0) dev: true + /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.14.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0): + resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==} + peerDependencies: + '@typescript-eslint/eslint-plugin': '>= 6' + '@typescript-eslint/parser': '>= 6' + eslint: '>= 3' + eslint-plugin-import: '>= 2' + dependencies: + '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0) + dev: true + + /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.19.0)(@typescript-eslint/parser@6.19.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0): + resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==} + peerDependencies: + '@typescript-eslint/eslint-plugin': '>= 6' + '@typescript-eslint/parser': '>= 6' + eslint: '>= 3' + eslint-plugin-import: '>= 2' + dependencies: + '@typescript-eslint/eslint-plugin': 6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0) + dev: true + /@misskey-dev/sharp-read-bmp@1.1.1: resolution: {integrity: sha512-X52BQYL/I9mafypQ+wBhst+BUlYiPWnHhKGcF6ybcYSLl+zhcV0q5mezIXHozhM0Sv0A7xCdrWmR7TCNxHLrtQ==} dependencies: @@ -5089,7 +5150,7 @@ packages: '@open-draft/until': 1.0.3 '@types/debug': 4.1.7 '@xmldom/xmldom': 0.8.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) headers-polyfill: 3.2.5 outvariant: 1.4.0 strict-event-emitter: 0.2.8 @@ -7992,7 +8053,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/responselike': 1.0.0 dev: false @@ -8025,7 +8086,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/content-disposition@0.5.8: @@ -8039,7 +8100,7 @@ packages: /@types/cross-spawn@6.0.2: resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/debug@4.1.7: @@ -8097,7 +8158,7 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -8125,13 +8186,13 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/http-cache-semantics@4.0.1: @@ -8212,7 +8273,7 @@ packages: /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: false /@types/lodash@4.14.191: @@ -8261,7 +8322,7 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 form-data: 3.0.1 /@types/node-fetch@3.0.3: @@ -8279,6 +8340,11 @@ packages: dependencies: undici-types: 5.26.5 + /@types/node@20.11.5: + resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==} + dependencies: + undici-types: 5.26.5 + /@types/node@20.9.1: resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==} dependencies: @@ -8381,7 +8447,7 @@ packages: /@types/readdir-glob@1.1.1: resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/rename@1.0.7: @@ -8395,7 +8461,7 @@ packages: /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: false /@types/sanitize-html@2.9.5: @@ -8421,7 +8487,7 @@ packages: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/serviceworker@0.0.67: @@ -8431,7 +8497,7 @@ packages: /@types/set-cookie-parser@2.4.3: resolution: {integrity: sha512-7QhnH7bi+6KAhBB+Auejz1uV9DHiopZqu7LfR/5gZZTkejJV5nYeZZpgfFoE0N8aDsXuiYpfKyfyMatCwQhyTQ==} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /@types/sharp@0.32.0: @@ -8534,7 +8600,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true optional: true @@ -8555,7 +8621,7 @@ packages: '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8584,7 +8650,65 @@ packages: '@typescript-eslint/type-utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.14.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.6.2 + '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/eslint-plugin@6.19.0(@typescript-eslint/parser@6.19.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.6.2 + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/type-utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.2.4 @@ -8610,7 +8734,7 @@ packages: '@typescript-eslint/types': 6.11.0 '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8631,7 +8755,28 @@ packages: '@typescript-eslint/types': 6.14.0 '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.14.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@6.19.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 typescript: 5.3.3 transitivePeerDependencies: @@ -8654,6 +8799,14 @@ packages: '@typescript-eslint/visitor-keys': 6.14.0 dev: true + /@typescript-eslint/scope-manager@6.19.0: + resolution: {integrity: sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/visitor-keys': 6.19.0 + dev: true + /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.3.3): resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8666,7 +8819,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.53.0 ts-api-utils: 1.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -8686,7 +8839,27 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3) '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) + eslint: 8.56.0 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/type-utils@6.19.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 ts-api-utils: 1.0.1(typescript@5.3.3) typescript: 5.3.3 @@ -8704,6 +8877,11 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true + /@typescript-eslint/types@6.19.0: + resolution: {integrity: sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + /@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.3): resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8715,7 +8893,7 @@ packages: dependencies: '@typescript-eslint/types': 6.11.0 '@typescript-eslint/visitor-keys': 6.11.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8736,7 +8914,7 @@ packages: dependencies: '@typescript-eslint/types': 6.14.0 '@typescript-eslint/visitor-keys': 6.14.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -8746,6 +8924,28 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@6.19.0(typescript@5.3.3): + resolution: {integrity: sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/visitor-keys': 6.19.0 + debug: 4.3.4(supports-color@5.5.0) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.3.3): resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8784,6 +8984,25 @@ packages: - typescript dev: true + /@typescript-eslint/utils@6.19.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.6 + '@typescript-eslint/scope-manager': 6.19.0 + '@typescript-eslint/types': 6.19.0 + '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.3.3) + eslint: 8.56.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@6.11.0: resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -8800,6 +9019,14 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@typescript-eslint/visitor-keys@6.19.0: + resolution: {integrity: sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.19.0 + eslint-visitor-keys: 3.4.3 + dev: true + /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true @@ -9193,7 +9420,7 @@ packages: engines: {node: '>= 6.0.0'} requiresBuild: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -9201,7 +9428,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -9587,7 +9814,7 @@ packages: resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} dependencies: archy: 1.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) fastq: 1.15.0 transitivePeerDependencies: - supports-color @@ -11036,7 +11263,6 @@ packages: dependencies: ms: 2.1.2 supports-color: 5.5.0 - dev: true /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -11049,6 +11275,7 @@ packages: dependencies: ms: 2.1.2 supports-color: 8.1.1 + dev: true /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -11265,7 +11492,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -11589,7 +11816,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -11806,6 +12033,35 @@ packages: - supports-color dev: true + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + debug: 3.2.7(supports-color@8.1.1) + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.11.0)(eslint@8.53.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} @@ -11876,6 +12132,41 @@ packages: - supports-color dev: true + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.0)(eslint@8.56.0): + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 6.19.0(eslint@8.56.0)(typescript@5.3.3) + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) + hasown: 2.0.0 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-plugin-vue@9.19.2(eslint@8.56.0): resolution: {integrity: sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==} engines: {node: ^14.17.0 || >=16.0.0} @@ -11927,7 +12218,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -11974,7 +12265,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -12604,7 +12895,7 @@ packages: debug: optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -13160,7 +13451,6 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -13298,7 +13588,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13360,7 +13650,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -13370,7 +13660,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -13379,7 +13669,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13389,7 +13679,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -13549,7 +13839,7 @@ packages: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -13990,7 +14280,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -14044,7 +14334,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 co: 4.6.0 dedent: 1.3.0 @@ -14133,6 +14423,46 @@ packages: - supports-color dev: true + /jest-config@29.7.0(@types/node@20.11.5): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.22.11 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.5 + babel-jest: 29.7.0(@babel/core@7.22.11) + chalk: 4.1.2 + ci-info: 3.7.1 + deepmerge: 4.2.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-diff@28.1.3: resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -14188,7 +14518,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -14218,7 +14548,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 - '@types/node': 20.10.5 + '@types/node': 20.11.5 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -14279,7 +14609,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 20.10.5 + '@types/node': 20.11.5 dev: true /jest-mock@29.7.0: @@ -14342,7 +14672,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -14373,7 +14703,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -14425,7 +14755,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 chalk: 4.1.2 ci-info: 3.7.1 graceful-fs: 4.2.11 @@ -14450,7 +14780,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.5 + '@types/node': 20.11.5 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -14469,7 +14799,7 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.10.5 + '@types/node': 20.11.5 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -14667,7 +14997,7 @@ packages: resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) rfdc: 1.3.0 uri-js: 4.4.1 transitivePeerDependencies: @@ -17278,7 +17608,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -18275,7 +18605,7 @@ packages: dependencies: '@hapi/hoek': 10.0.1 '@hapi/wreck': 18.0.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) joi: 17.7.0 transitivePeerDependencies: - supports-color @@ -18475,7 +18805,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -18628,7 +18958,7 @@ packages: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -18892,7 +19222,6 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -19515,7 +19844,7 @@ packages: chalk: 4.1.2 cli-highlight: 2.1.11 date-fns: 2.30.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) dotenv: 16.0.3 glob: 8.1.0 ioredis: 5.3.2 @@ -19880,7 +20209,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 @@ -19992,7 +20321,7 @@ packages: acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.10 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) happy-dom: 10.0.3 local-pkg: 0.4.3 magic-string: 0.30.3 @@ -20074,7 +20403,7 @@ packages: peerDependencies: eslint: '>=6.0.0' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3b2ecec7fd..3a03a58253 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,3 +4,4 @@ packages: - 'packages/sw' - 'packages/misskey-js' - 'packages/misskey-js/generator' + - 'packages/misskey-reversi' From 7881f06be060dd955499a6beff3ad1967ed628f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Sat, 20 Jan 2024 08:11:59 +0900 Subject: [PATCH 127/334] refactor: deprecate i18n.t (#13039) * refactor: deprecate i18n.t * revert: deprecate i18n.t This reverts commit 7dbf873a2f745040ee723df5db659acacff84e12. * chore: reimpl --- locales/generateDTS.js | 78 +- locales/index.d.ts | 6878 ++++++++++++++++- packages/frontend/src/boot/main-boot.ts | 2 +- .../src/components/MkAnnouncementDialog.vue | 2 +- .../frontend/src/components/MkCwButton.vue | 4 +- .../src/components/MkDateSeparatedList.vue | 2 +- packages/frontend/src/components/MkDialog.vue | 4 +- packages/frontend/src/components/MkDrive.vue | 4 +- .../src/components/MkFollowButton.vue | 2 +- packages/frontend/src/components/MkNote.vue | 2 +- .../src/components/MkNoteDetailed.vue | 2 +- .../src/components/MkNotification.vue | 4 +- .../components/MkNotificationSelectWindow.vue | 2 +- packages/frontend/src/components/MkPoll.vue | 15 +- .../frontend/src/components/MkPollEditor.vue | 2 +- .../src/components/MkSignupDialog.form.vue | 2 +- .../src/components/MkSignupDialog.rules.vue | 6 +- .../src/components/MkSubNoteContent.vue | 2 +- .../src/components/MkTokenGenerateWindow.vue | 4 +- .../src/components/MkTutorialDialog.vue | 2 +- .../MkUserAnnouncementEditDialog.vue | 2 +- .../components/MkUserSetupDialog.Profile.vue | 2 +- .../src/components/MkUserSetupDialog.vue | 4 +- .../frontend/src/components/MkWidgets.vue | 4 +- .../frontend/src/components/global/I18n.vue | 46 + .../components/global/MkTime.stories.impl.ts | 10 +- .../frontend/src/components/global/MkTime.vue | 28 +- .../frontend/src/components/global/i18n.ts | 29 - packages/frontend/src/components/index.ts | 2 +- packages/frontend/src/pages/about.vue | 2 +- packages/frontend/src/pages/admin-file.vue | 2 +- packages/frontend/src/pages/admin-user.vue | 8 +- packages/frontend/src/pages/admin/ads.vue | 2 +- .../src/pages/admin/announcements.vue | 4 +- .../frontend/src/pages/admin/branding.vue | 8 +- packages/frontend/src/pages/admin/relays.vue | 2 +- .../frontend/src/pages/admin/roles.role.vue | 2 +- packages/frontend/src/pages/announcements.vue | 2 +- packages/frontend/src/pages/auth.form.vue | 6 +- packages/frontend/src/pages/auth.vue | 2 +- .../frontend/src/pages/avatar-decorations.vue | 2 +- .../frontend/src/pages/channel-editor.vue | 2 +- packages/frontend/src/pages/clip.vue | 2 +- .../frontend/src/pages/drive.file.info.vue | 2 +- .../frontend/src/pages/drop-and-fusion.vue | 2 +- .../frontend/src/pages/emoji-edit-dialog.vue | 2 +- .../frontend/src/pages/flash/flash-edit.vue | 2 +- packages/frontend/src/pages/follow.vue | 2 +- packages/frontend/src/pages/instance-info.vue | 4 +- packages/frontend/src/pages/invite.vue | 4 +- packages/frontend/src/pages/miauth.vue | 6 +- .../frontend/src/pages/my-antennas/editor.vue | 2 +- .../frontend/src/pages/my-lists/index.vue | 2 +- packages/frontend/src/pages/my-lists/list.vue | 4 +- packages/frontend/src/pages/note.vue | 2 +- packages/frontend/src/pages/notifications.vue | 2 +- packages/frontend/src/pages/oauth.vue | 6 +- .../src/pages/page-editor/page-editor.vue | 2 +- .../frontend/src/pages/reversi/game.board.vue | 8 +- packages/frontend/src/pages/settings/2fa.vue | 2 +- packages/frontend/src/pages/settings/apps.vue | 2 +- .../src/pages/settings/avatar-decoration.vue | 2 +- .../frontend/src/pages/settings/general.vue | 6 +- .../frontend/src/pages/settings/migration.vue | 4 +- .../pages/settings/mute-block.word-mute.vue | 2 +- .../src/pages/settings/notifications.vue | 2 +- .../frontend/src/pages/settings/profile.vue | 4 +- .../frontend/src/pages/settings/sounds.vue | 4 +- .../src/pages/settings/theme.install.vue | 2 +- .../src/pages/settings/webhook.edit.vue | 2 +- .../frontend/src/pages/signup-complete.vue | 2 +- packages/frontend/src/pages/theme-editor.vue | 2 +- packages/frontend/src/pages/user/home.vue | 2 +- .../src/scripts/get-drive-file-menu.ts | 2 +- .../frontend/src/scripts/get-note-menu.ts | 4 +- .../frontend/src/scripts/get-note-summary.ts | 2 +- packages/frontend/src/scripts/i18n.ts | 221 +- packages/frontend/src/ui/deck.vue | 6 +- .../frontend/src/widgets/WidgetCalendar.vue | 8 +- .../frontend/src/widgets/WidgetSlideshow.vue | 2 +- .../frontend/src/widgets/WidgetTimeline.vue | 2 +- .../frontend/src/widgets/WidgetTrends.vue | 2 +- packages/frontend/tsconfig.json | 1 + packages/frontend/vite.config.ts | 1 + 84 files changed, 7311 insertions(+), 222 deletions(-) create mode 100644 packages/frontend/src/components/global/I18n.vue delete mode 100644 packages/frontend/src/components/global/i18n.ts diff --git a/locales/generateDTS.js b/locales/generateDTS.js index 6eb5bd630d..49807144ec 100644 --- a/locales/generateDTS.js +++ b/locales/generateDTS.js @@ -16,32 +16,40 @@ function createMemberType(item) { item.matchAll(parameterRegExp), ([, parameter]) => parameter, ); - if (!parameters.length) { - return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); - } - return ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('ParameterizedString'), - [ - ts.factory.createUnionTypeNode( - parameters.map((parameter) => - ts.factory.createLiteralTypeNode( - ts.factory.createStringLiteral(parameter), + return parameters.length + ? ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('ParameterizedString'), + [ + ts.factory.createUnionTypeNode( + parameters.map((parameter) => + ts.factory.createStringLiteral(parameter), + ), ), - ), - ), - ], - ); + ], + ) + : ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword); } function createMembers(record) { - return Object.entries(record).map(([k, v]) => - ts.factory.createPropertySignature( + return Object.entries(record).map(([k, v]) => { + const node = ts.factory.createPropertySignature( undefined, ts.factory.createStringLiteral(k), undefined, createMemberType(v), - ), - ); + ); + if (typeof v === 'string') { + ts.addSyntheticLeadingComment( + node, + ts.SyntaxKind.MultiLineCommentTrivia, + `* + * ${v.replace(/\n/g, '\n * ')} + `, + true, + ); + } + return node; + }); } export default function generateDTS() { @@ -72,10 +80,8 @@ export default function generateDTS() { ts.factory.createTypeParameterDeclaration( undefined, ts.factory.createIdentifier('T'), - ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier('string'), - undefined, - ), + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), ), ], undefined, @@ -115,7 +121,6 @@ export default function generateDTS() { ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), ts.factory.createTypeReferenceNode( ts.factory.createIdentifier('ParameterizedString'), - [ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)], ), ts.factory.createTypeReferenceNode( ts.factory.createIdentifier('ILocale'), @@ -187,6 +192,24 @@ export default function generateDTS() { ), 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 .createPrinter({ newLine: ts.NewLineKind.LineFeed, @@ -203,12 +226,5 @@ export default function generateDTS() { ), ); - fs.writeFileSync( - `${__dirname}/index.d.ts`, - `/* eslint-disable */ -// This file is generated by locales/generateDTS.js -// Do not edit this file directly. -${printed}`, - 'utf-8', - ); + fs.writeFileSync(`${__dirname}/index.d.ts`, printed, 'utf-8'); } diff --git a/locales/index.d.ts b/locales/index.d.ts index 85e0c6b244..f7f952175f 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2,2670 +2,9544 @@ // This file is generated by locales/generateDTS.js // Do not edit this file directly. declare const kParameters: unique symbol; -export interface ParameterizedString { +export interface ParameterizedString { [kParameters]: T; } export interface ILocale { - [_: string]: string | ParameterizedString | ILocale; + [_: string]: string | ParameterizedString | ILocale; } export interface Locale extends ILocale { + /** + * 日本語 + */ "_lang_": string; + /** + * ノートでつながるネットワーク + */ "headlineMisskey": string; + /** + * ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスです。 + * 「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡 + * 「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍 + * 新しい世界を探検しよう🚀 + */ "introMisskey": string; + /** + * {name}は、オープンソースのプラットフォームMisskeyのサーバーのひとつです。 + */ "poweredByMisskeyDescription": ParameterizedString<"name">; + /** + * {month}月 {day}日 + */ "monthAndDay": ParameterizedString<"month" | "day">; + /** + * 検索 + */ "search": string; + /** + * 通知 + */ "notifications": string; + /** + * ユーザー名 + */ "username": string; + /** + * パスワード + */ "password": string; + /** + * パスワードを忘れた + */ "forgotPassword": string; + /** + * 連合に照会中 + */ "fetchingAsApObject": string; + /** + * OK + */ "ok": string; + /** + * わかった + */ "gotIt": string; + /** + * キャンセル + */ "cancel": string; + /** + * やめておく + */ "noThankYou": string; + /** + * ユーザー名を入力 + */ "enterUsername": string; + /** + * {user}がリノート + */ "renotedBy": ParameterizedString<"user">; + /** + * ノートはありません + */ "noNotes": string; + /** + * 通知はありません + */ "noNotifications": string; + /** + * サーバー + */ "instance": string; + /** + * 設定 + */ "settings": string; + /** + * 通知の設定 + */ "notificationSettings": string; + /** + * 基本設定 + */ "basicSettings": string; + /** + * その他の設定 + */ "otherSettings": string; + /** + * ウィンドウで開く + */ "openInWindow": string; + /** + * プロフィール + */ "profile": string; + /** + * タイムライン + */ "timeline": string; + /** + * 自己紹介はありません + */ "noAccountDescription": string; + /** + * ログイン + */ "login": string; + /** + * ログイン中 + */ "loggingIn": string; + /** + * ログアウト + */ "logout": string; + /** + * 新規登録 + */ "signup": string; + /** + * アップロード中 + */ "uploading": string; + /** + * 保存 + */ "save": string; + /** + * ユーザー + */ "users": string; + /** + * ユーザーを追加 + */ "addUser": string; + /** + * お気に入り + */ "favorite": string; + /** + * お気に入り + */ "favorites": string; + /** + * お気に入り解除 + */ "unfavorite": string; + /** + * お気に入りに登録しました。 + */ "favorited": string; + /** + * 既にお気に入りに登録されています。 + */ "alreadyFavorited": string; + /** + * お気に入りに登録できませんでした。 + */ "cantFavorite": string; + /** + * ピン留め + */ "pin": string; + /** + * ピン留め解除 + */ "unpin": string; + /** + * 内容をコピー + */ "copyContent": string; + /** + * リンクをコピー + */ "copyLink": string; + /** + * リノートのリンクをコピー + */ "copyLinkRenote": string; + /** + * 削除 + */ "delete": string; + /** + * 削除して編集 + */ "deleteAndEdit": string; + /** + * このノートを削除してもう一度編集しますか?このノートへのリアクション、リノート、返信も全て削除されます。 + */ "deleteAndEditConfirm": string; + /** + * リストに追加 + */ "addToList": string; + /** + * アンテナに追加 + */ "addToAntenna": string; + /** + * メッセージを送信 + */ "sendMessage": string; + /** + * RSSをコピー + */ "copyRSS": string; + /** + * ユーザー名をコピー + */ "copyUsername": string; + /** + * ユーザーIDをコピー + */ "copyUserId": string; + /** + * ノートIDをコピー + */ "copyNoteId": string; + /** + * ファイルIDをコピー + */ "copyFileId": string; + /** + * フォルダーIDをコピー + */ "copyFolderId": string; + /** + * プロフィールURLをコピー + */ "copyProfileUrl": string; + /** + * ユーザーを検索 + */ "searchUser": string; + /** + * 返信 + */ "reply": string; + /** + * もっと見る + */ "loadMore": string; + /** + * もっと見る + */ "showMore": string; + /** + * 閉じる + */ "showLess": string; + /** + * フォローされました + */ "youGotNewFollower": string; + /** + * フォローリクエストされました + */ "receiveFollowRequest": string; + /** + * フォローが承認されました + */ "followRequestAccepted": string; + /** + * メンション + */ "mention": string; + /** + * あなた宛て + */ "mentions": string; + /** + * ダイレクト投稿 + */ "directNotes": string; + /** + * インポートとエクスポート + */ "importAndExport": string; + /** + * インポート + */ "import": string; + /** + * エクスポート + */ "export": string; + /** + * ファイル + */ "files": string; + /** + * ダウンロード + */ "download": string; + /** + * ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。 + */ "driveFileDeleteConfirm": ParameterizedString<"name">; + /** + * {name}のフォローを解除しますか? + */ "unfollowConfirm": ParameterizedString<"name">; + /** + * エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。 + */ "exportRequested": string; + /** + * インポートをリクエストしました。これには時間がかかる場合があります。 + */ "importRequested": string; + /** + * リスト + */ "lists": string; + /** + * リストはありません + */ "noLists": string; + /** + * ノート + */ "note": string; + /** + * ノート + */ "notes": string; + /** + * フォロー + */ "following": string; + /** + * フォロワー + */ "followers": string; + /** + * フォローされています + */ "followsYou": string; + /** + * リスト作成 + */ "createList": string; + /** + * リストの管理 + */ "manageLists": string; + /** + * エラー + */ "error": string; + /** + * 問題が発生しました + */ "somethingHappened": string; + /** + * 再試行 + */ "retry": string; + /** + * ページの読み込みに失敗しました。 + */ "pageLoadError": string; + /** + * これは通常、ネットワークまたはブラウザキャッシュが原因です。キャッシュをクリアするか、しばらく待ってから再度試してください。 + */ "pageLoadErrorDescription": string; + /** + * サーバーの応答がありません。しばらく待ってから再度試してください。 + */ "serverIsDead": string; + /** + * このページを表示するためには、リロードして新しいバージョンのクライアントをご利用ください。 + */ "youShouldUpgradeClient": string; + /** + * リスト名を入力 + */ "enterListName": string; + /** + * プライバシー + */ "privacy": string; + /** + * フォローを承認制にする + */ "makeFollowManuallyApprove": string; + /** + * デフォルトの公開範囲 + */ "defaultNoteVisibility": string; + /** + * フォロー + */ "follow": string; + /** + * フォロー申請 + */ "followRequest": string; + /** + * フォロー申請 + */ "followRequests": string; + /** + * フォロー解除 + */ "unfollow": string; + /** + * フォロー許可待ち + */ "followRequestPending": string; + /** + * 絵文字を入力 + */ "enterEmoji": string; + /** + * リノート + */ "renote": string; + /** + * リノート解除 + */ "unrenote": string; + /** + * リノートしました。 + */ "renoted": string; + /** + * この投稿はリノートできません。 + */ "cantRenote": string; + /** + * リノートをリノートすることはできません。 + */ "cantReRenote": string; + /** + * 引用 + */ "quote": string; + /** + * チャンネル内リノート + */ "inChannelRenote": string; + /** + * チャンネル内引用 + */ "inChannelQuote": string; + /** + * ピン留めされたノート + */ "pinnedNote": string; + /** + * ピン留め + */ "pinned": string; + /** + * あなた + */ "you": string; + /** + * クリックして表示 + */ "clickToShow": string; + /** + * センシティブ + */ "sensitive": string; + /** + * 追加 + */ "add": string; + /** + * リアクション + */ "reaction": string; + /** + * リアクション + */ "reactions": string; + /** + * 絵文字ピッカー + */ "emojiPicker": string; + /** + * リアクション時にピン留め表示する絵文字を設定できます + */ "pinnedEmojisForReactionSettingDescription": string; + /** + * 絵文字入力時にピン留め表示する絵文字を設定できます + */ "pinnedEmojisSettingDescription": string; + /** + * ピッカーの表示 + */ "emojiPickerDisplay": string; + /** + * リアクション設定から上書きする + */ "overwriteFromPinnedEmojisForReaction": string; + /** + * 全般設定から上書きする + */ "overwriteFromPinnedEmojis": string; + /** + * ドラッグして並び替え、クリックして削除、+を押して追加します。 + */ "reactionSettingDescription2": string; + /** + * 公開範囲を記憶する + */ "rememberNoteVisibility": string; + /** + * 添付取り消し + */ "attachCancel": string; + /** + * センシティブとして設定 + */ "markAsSensitive": string; + /** + * センシティブを解除する + */ "unmarkAsSensitive": string; + /** + * ファイル名を入力 + */ "enterFileName": string; + /** + * ミュート + */ "mute": string; + /** + * ミュート解除 + */ "unmute": string; + /** + * リノートをミュート + */ "renoteMute": string; + /** + * リノートのミュートを解除 + */ "renoteUnmute": string; + /** + * ブロック + */ "block": string; + /** + * ブロック解除 + */ "unblock": string; + /** + * 凍結 + */ "suspend": string; + /** + * 解凍 + */ "unsuspend": string; + /** + * ブロックしますか? + */ "blockConfirm": string; + /** + * ブロック解除しますか? + */ "unblockConfirm": string; + /** + * 凍結しますか? + */ "suspendConfirm": string; + /** + * 解凍しますか? + */ "unsuspendConfirm": string; + /** + * リストを選択 + */ "selectList": string; + /** + * リストを編集 + */ "editList": string; + /** + * チャンネルを選択 + */ "selectChannel": string; + /** + * アンテナを選択 + */ "selectAntenna": string; + /** + * アンテナを編集 + */ "editAntenna": string; + /** + * ウィジェットを選択 + */ "selectWidget": string; + /** + * ウィジェットを編集 + */ "editWidgets": string; + /** + * 編集を終了 + */ "editWidgetsExit": string; + /** + * カスタム絵文字 + */ "customEmojis": string; + /** + * 絵文字 + */ "emoji": string; + /** + * 絵文字 + */ "emojis": string; + /** + * 絵文字名 + */ "emojiName": string; + /** + * 絵文字画像URL + */ "emojiUrl": string; + /** + * 絵文字を追加 + */ "addEmoji": string; + /** + * おすすめ設定 + */ "settingGuide": string; + /** + * リモートのファイルをキャッシュする + */ "cacheRemoteFiles": string; + /** + * この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持しますが、画像のサムネイル生成やユーザーのプライバシー保護のために、default.ymlでproxyRemoteFilesをtrueにすることをお勧めします。 + */ "cacheRemoteFilesDescription": string; + /** + * ファイル管理の🗑️ボタンで全てのキャッシュを削除できます。 + */ "youCanCleanRemoteFilesCache": string; + /** + * リモートのセンシティブなファイルをキャッシュする + */ "cacheRemoteSensitiveFiles": string; + /** + * この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになります。 + */ "cacheRemoteSensitiveFilesDescription": string; + /** + * Botとして設定 + */ "flagAsBot": string; + /** + * このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。 + */ "flagAsBotDescription": string; + /** + * にゃああああああああああああああ!!!!!!!!!!!! + */ "flagAsCat": string; + /** + * にゃにゃにゃ?? + */ "flagAsCatDescription": string; + /** + * タイムラインにノートへの返信を表示する + */ "flagShowTimelineReplies": string; + /** + * オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。 + */ "flagShowTimelineRepliesDescription": string; + /** + * フォロー中ユーザーからのフォロリクを自動承認 + */ "autoAcceptFollowed": string; + /** + * アカウントを追加 + */ "addAccount": string; + /** + * アカウントリストの情報を更新 + */ "reloadAccountsList": string; + /** + * ログインに失敗しました + */ "loginFailed": string; + /** + * リモートで表示 + */ "showOnRemote": string; + /** + * 全般 + */ "general": string; + /** + * 壁紙 + */ "wallpaper": string; + /** + * 壁紙を設定 + */ "setWallpaper": string; + /** + * 壁紙を削除 + */ "removeWallpaper": string; + /** + * 検索: {q} + */ "searchWith": ParameterizedString<"q">; + /** + * リストがありません + */ "youHaveNoLists": string; + /** + * {name}をフォローしますか? + */ "followConfirm": ParameterizedString<"name">; + /** + * プロキシアカウント + */ "proxyAccount": string; + /** + * プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。 + */ "proxyAccountDescription": string; + /** + * ホスト + */ "host": string; + /** + * ユーザーを選択 + */ "selectUser": string; + /** + * 宛先 + */ "recipient": string; + /** + * 注釈 + */ "annotation": string; + /** + * 連合 + */ "federation": string; + /** + * サーバー + */ "instances": string; + /** + * 初観測 + */ "registeredAt": string; + /** + * 直近のリクエスト受信 + */ "latestRequestReceivedAt": string; + /** + * 直近のステータス + */ "latestStatus": string; + /** + * ストレージ使用量 + */ "storageUsage": string; + /** + * チャート + */ "charts": string; + /** + * 1時間ごと + */ "perHour": string; + /** + * 1日ごと + */ "perDay": string; + /** + * アクティビティの配送を停止 + */ "stopActivityDelivery": string; + /** + * このサーバーをブロック + */ "blockThisInstance": string; + /** + * サーバーをサイレンス + */ "silenceThisInstance": string; + /** + * 操作 + */ "operations": string; + /** + * ソフトウェア + */ "software": string; + /** + * バージョン + */ "version": string; + /** + * メタデータ + */ "metadata": string; + /** + * {n}つのファイル + */ "withNFiles": ParameterizedString<"n">; + /** + * モニター + */ "monitor": string; + /** + * ジョブキュー + */ "jobQueue": string; + /** + * CPUとメモリ + */ "cpuAndMemory": string; + /** + * ネットワーク + */ "network": string; + /** + * ディスク + */ "disk": string; + /** + * サーバー情報 + */ "instanceInfo": string; + /** + * 統計 + */ "statistics": string; + /** + * キューをクリア + */ "clearQueue": string; + /** + * キューをクリアしますか? + */ "clearQueueConfirmTitle": string; + /** + * 未配達の投稿は配送されなくなります。通常この操作を行う必要はありません。 + */ "clearQueueConfirmText": string; + /** + * キャッシュをクリア + */ "clearCachedFiles": string; + /** + * キャッシュされたリモートファイルをすべて削除しますか? + */ "clearCachedFilesConfirm": string; + /** + * ブロックしたサーバー + */ "blockedInstances": string; + /** + * ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。 + */ "blockedInstancesDescription": string; + /** + * サイレンスしたサーバー + */ "silencedInstances": string; + /** + * サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。 + */ "silencedInstancesDescription": string; + /** + * ミュートとブロック + */ "muteAndBlock": string; + /** + * ミュートしたユーザー + */ "mutedUsers": string; + /** + * ブロックしたユーザー + */ "blockedUsers": string; + /** + * ユーザーはいません + */ "noUsers": string; + /** + * プロフィールを編集 + */ "editProfile": string; + /** + * このノートを削除しますか? + */ "noteDeleteConfirm": string; + /** + * これ以上ピン留めできません + */ "pinLimitExceeded": string; + /** + * Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。 + */ "intro": string; + /** + * 完了 + */ "done": string; + /** + * 処理中 + */ "processing": string; + /** + * プレビュー + */ "preview": string; + /** + * デフォルト + */ "default": string; + /** + * デフォルト: {value} + */ "defaultValueIs": ParameterizedString<"value">; + /** + * 絵文字はありません + */ "noCustomEmojis": string; + /** + * ジョブはありません + */ "noJobs": string; + /** + * 連合中 + */ "federating": string; + /** + * ブロック中 + */ "blocked": string; + /** + * 配信停止 + */ "suspended": string; + /** + * 全て + */ "all": string; + /** + * 購読中 + */ "subscribing": string; + /** + * 配信中 + */ "publishing": string; + /** + * 応答なし + */ "notResponding": string; + /** + * サーバーのフォロー + */ "instanceFollowing": string; + /** + * サーバーのフォロワー + */ "instanceFollowers": string; + /** + * サーバーのユーザー + */ "instanceUsers": string; + /** + * パスワードを変更 + */ "changePassword": string; + /** + * セキュリティ + */ "security": string; + /** + * 入力が一致しません。 + */ "retypedNotMatch": string; + /** + * 現在のパスワード + */ "currentPassword": string; + /** + * 新しいパスワード + */ "newPassword": string; + /** + * 新しいパスワード(再入力) + */ "newPasswordRetype": string; + /** + * ファイルを添付 + */ "attachFile": string; + /** + * もっと! + */ "more": string; + /** + * ハイライト + */ "featured": string; + /** + * ユーザー名かユーザーID + */ "usernameOrUserId": string; + /** + * ユーザーが見つかりません + */ "noSuchUser": string; + /** + * 照会 + */ "lookup": string; + /** + * お知らせ + */ "announcements": string; + /** + * 画像URL + */ "imageUrl": string; + /** + * 削除 + */ "remove": string; + /** + * 削除しました + */ "removed": string; + /** + * 「{x}」を削除しますか? + */ "removeAreYouSure": ParameterizedString<"x">; + /** + * 「{x}」を削除しますか? + */ "deleteAreYouSure": ParameterizedString<"x">; + /** + * リセットしますか? + */ "resetAreYouSure": string; + /** + * よろしいですか? + */ "areYouSure": string; + /** + * 保存しました + */ "saved": string; + /** + * チャット + */ "messaging": string; + /** + * アップロード + */ "upload": string; + /** + * オリジナル画像を保持 + */ "keepOriginalUploading": string; + /** + * 画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。 + */ "keepOriginalUploadingDescription": string; + /** + * ドライブから + */ "fromDrive": string; + /** + * URLから + */ "fromUrl": string; + /** + * URLアップロード + */ "uploadFromUrl": string; + /** + * アップロードしたいファイルのURL + */ "uploadFromUrlDescription": string; + /** + * アップロードをリクエストしました + */ "uploadFromUrlRequested": string; + /** + * アップロードが完了するまで時間がかかる場合があります。 + */ "uploadFromUrlMayTakeTime": string; + /** + * みつける + */ "explore": string; + /** + * 既読 + */ "messageRead": string; + /** + * これより過去の履歴はありません + */ "noMoreHistory": string; + /** + * チャットを開始 + */ "startMessaging": string; + /** + * {n}人が読みました + */ "nUsersRead": ParameterizedString<"n">; + /** + * {0}に同意 + */ "agreeTo": ParameterizedString<"0">; + /** + * 同意する + */ "agree": string; + /** + * 下記に同意する + */ "agreeBelow": string; + /** + * 基本的な注意事項 + */ "basicNotesBeforeCreateAccount": string; + /** + * 利用規約 + */ "termsOfService": string; + /** + * 始める + */ "start": string; + /** + * ホーム + */ "home": string; + /** + * リモートユーザーのため、情報が不完全です。 + */ "remoteUserCaution": string; + /** + * アクティビティ + */ "activity": string; + /** + * 画像 + */ "images": string; + /** + * 画像 + */ "image": string; + /** + * 誕生日 + */ "birthday": string; + /** + * {age}歳 + */ "yearsOld": ParameterizedString<"age">; + /** + * 登録日 + */ "registeredDate": string; + /** + * 場所 + */ "location": string; + /** + * テーマ + */ "theme": string; + /** + * ライトモードで使うテーマ + */ "themeForLightMode": string; + /** + * ダークモードで使うテーマ + */ "themeForDarkMode": string; + /** + * ライト + */ "light": string; + /** + * ダーク + */ "dark": string; + /** + * 明るいテーマ + */ "lightThemes": string; + /** + * 暗いテーマ + */ "darkThemes": string; + /** + * デバイスのダークモードと同期する + */ "syncDeviceDarkMode": string; + /** + * ドライブ + */ "drive": string; + /** + * ファイル名 + */ "fileName": string; + /** + * ファイルを選択 + */ "selectFile": string; + /** + * ファイルを選択 + */ "selectFiles": string; + /** + * フォルダーを選択 + */ "selectFolder": string; + /** + * フォルダーを選択 + */ "selectFolders": string; + /** + * ファイル名を変更 + */ "renameFile": string; + /** + * フォルダー名 + */ "folderName": string; + /** + * フォルダーを作成 + */ "createFolder": string; + /** + * フォルダー名を変更 + */ "renameFolder": string; + /** + * フォルダーを削除 + */ "deleteFolder": string; + /** + * フォルダー + */ "folder": string; + /** + * ファイルを追加 + */ "addFile": string; + /** + * ドライブは空です + */ "emptyDrive": string; + /** + * フォルダーは空です + */ "emptyFolder": string; + /** + * 削除できません + */ "unableToDelete": string; + /** + * 新しいファイル名を入力してください + */ "inputNewFileName": string; + /** + * 新しいキャプションを入力してください + */ "inputNewDescription": string; + /** + * 新しいフォルダ名を入力してください + */ "inputNewFolderName": string; + /** + * 移動先のフォルダーは、移動するフォルダーのサブフォルダーです。 + */ "circularReferenceFolder": string; + /** + * このフォルダは空でないため、削除できません。 + */ "hasChildFilesOrFolders": string; + /** + * URLをコピー + */ "copyUrl": string; + /** + * 名前を変更 + */ "rename": string; + /** + * アイコン + */ "avatar": string; + /** + * バナー + */ "banner": string; + /** + * センシティブなメディアの表示 + */ "displayOfSensitiveMedia": string; + /** + * サーバーとの接続が失われたとき + */ "whenServerDisconnected": string; + /** + * サーバーから切断されました + */ "disconnectedFromServer": string; + /** + * リロード + */ "reload": string; + /** + * なにもしない + */ "doNothing": string; + /** + * リロードしますか? + */ "reloadConfirm": string; + /** + * ウォッチ + */ "watch": string; + /** + * ウォッチ解除 + */ "unwatch": string; + /** + * 許可 + */ "accept": string; + /** + * 拒否 + */ "reject": string; + /** + * 通常 + */ "normal": string; + /** + * サーバー名 + */ "instanceName": string; + /** + * サーバーの紹介 + */ "instanceDescription": string; + /** + * 管理者の名前 + */ "maintainerName": string; + /** + * 管理者のメールアドレス + */ "maintainerEmail": string; + /** + * 利用規約URL + */ "tosUrl": string; + /** + * 今年 + */ "thisYear": string; + /** + * 今月 + */ "thisMonth": string; + /** + * 今日 + */ "today": string; + /** + * {day}日 + */ "dayX": ParameterizedString<"day">; + /** + * {month}月 + */ "monthX": ParameterizedString<"month">; + /** + * {year}年 + */ "yearX": ParameterizedString<"year">; + /** + * ページ + */ "pages": string; + /** + * 連携 + */ "integration": string; + /** + * 接続する + */ "connectService": string; + /** + * 切断する + */ "disconnectService": string; + /** + * ローカルタイムラインを有効にする + */ "enableLocalTimeline": string; + /** + * グローバルタイムラインを有効にする + */ "enableGlobalTimeline": string; + /** + * これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。 + */ "disablingTimelinesInfo": string; + /** + * 登録 + */ "registration": string; + /** + * 誰でも新規登録できるようにする + */ "enableRegistration": string; + /** + * 招待 + */ "invite": string; + /** + * ローカルユーザーひとりあたりのドライブ容量 + */ "driveCapacityPerLocalAccount": string; + /** + * リモートユーザーひとりあたりのドライブ容量 + */ "driveCapacityPerRemoteAccount": string; + /** + * メガバイト単位 + */ "inMb": string; + /** + * バナー画像のURL + */ "bannerUrl": string; + /** + * 背景画像のURL + */ "backgroundImageUrl": string; + /** + * 基本情報 + */ "basicInfo": string; + /** + * ピン留めユーザー + */ "pinnedUsers": string; + /** + * 「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。 + */ "pinnedUsersDescription": string; + /** + * ピン留めページ + */ "pinnedPages": string; + /** + * サーバーのトップページにピン留めしたいページのパスを改行で区切って記述します。 + */ "pinnedPagesDescription": string; + /** + * ピン留めするクリップのID + */ "pinnedClipId": string; + /** + * ピン留めされたノート + */ "pinnedNotes": string; + /** + * hCaptcha + */ "hcaptcha": string; + /** + * hCaptchaを有効にする + */ "enableHcaptcha": string; + /** + * サイトキー + */ "hcaptchaSiteKey": string; + /** + * シークレットキー + */ "hcaptchaSecretKey": string; + /** + * mCaptcha + */ "mcaptcha": string; + /** + * mCaptchaを有効にする + */ "enableMcaptcha": string; + /** + * サイトキー + */ "mcaptchaSiteKey": string; + /** + * シークレットキー + */ "mcaptchaSecretKey": string; + /** + * mCaptchaのインスタンスのURL + */ "mcaptchaInstanceUrl": string; + /** + * reCAPTCHA + */ "recaptcha": string; + /** + * reCAPTCHAを有効にする + */ "enableRecaptcha": string; + /** + * サイトキー + */ "recaptchaSiteKey": string; + /** + * シークレットキー + */ "recaptchaSecretKey": string; + /** + * Turnstile + */ "turnstile": string; + /** + * Turnstileを有効にする + */ "enableTurnstile": string; + /** + * サイトキー + */ "turnstileSiteKey": string; + /** + * シークレットキー + */ "turnstileSecretKey": string; + /** + * 複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。 + */ "avoidMultiCaptchaConfirm": string; + /** + * アンテナ + */ "antennas": string; + /** + * アンテナの管理 + */ "manageAntennas": string; + /** + * 名前 + */ "name": string; + /** + * 受信ソース + */ "antennaSource": string; + /** + * 受信キーワード + */ "antennaKeywords": string; + /** + * 除外キーワード + */ "antennaExcludeKeywords": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります + */ "antennaKeywordsDescription": string; + /** + * 新しいノートを通知する + */ "notifyAntenna": string; + /** + * ファイルが添付されたノートのみ + */ "withFileAntenna": string; + /** + * ブラウザへのプッシュ通知を有効にする + */ "enableServiceworker": string; + /** + * ユーザー名を改行で区切って指定します + */ "antennaUsersDescription": string; + /** + * 大文字小文字を区別する + */ "caseSensitive": string; + /** + * 返信を含む + */ "withReplies": string; + /** + * 次のアカウントに接続されています + */ "connectedTo": string; + /** + * 投稿と返信 + */ "notesAndReplies": string; + /** + * ファイル付き + */ "withFiles": string; + /** + * サイレンス + */ "silence": string; + /** + * サイレンスしますか? + */ "silenceConfirm": string; + /** + * サイレンス解除 + */ "unsilence": string; + /** + * サイレンス解除しますか? + */ "unsilenceConfirm": string; + /** + * 人気のユーザー + */ "popularUsers": string; + /** + * 最近投稿したユーザー + */ "recentlyUpdatedUsers": string; + /** + * 最近登録したユーザー + */ "recentlyRegisteredUsers": string; + /** + * 最近発見されたユーザー + */ "recentlyDiscoveredUsers": string; + /** + * {count}のユーザーがいます + */ "exploreUsersCount": ParameterizedString<"count">; + /** + * Fediverseを探索 + */ "exploreFediverse": string; + /** + * 人気のタグ + */ "popularTags": string; + /** + * リスト + */ "userList": string; + /** + * 情報 + */ "about": string; + /** + * Misskeyについて + */ "aboutMisskey": string; + /** + * 管理者 + */ "administrator": string; + /** + * 確認コード + */ "token": string; + /** + * 二要素認証 + */ "2fa": string; + /** + * 二要素認証のセットアップ + */ "setupOf2fa": string; + /** + * 認証アプリ + */ "totp": string; + /** + * 認証アプリを使ってワンタイムパスワードを入力 + */ "totpDescription": string; + /** + * モデレーター + */ "moderator": string; + /** + * モデレーション + */ "moderation": string; + /** + * モデレーションノート + */ "moderationNote": string; + /** + * モデレーションノートを追加する + */ "addModerationNote": string; + /** + * モデログ + */ "moderationLogs": string; + /** + * {n}人が投稿 + */ "nUsersMentioned": ParameterizedString<"n">; + /** + * セキュリティキー・パスキー + */ "securityKeyAndPasskey": string; + /** + * セキュリティキー + */ "securityKey": string; + /** + * 最後の使用 + */ "lastUsed": string; + /** + * 最後の使用: {t} + */ "lastUsedAt": ParameterizedString<"t">; + /** + * 登録を解除 + */ "unregister": string; + /** + * パスワードレスログイン + */ "passwordLessLogin": string; + /** + * パスワードを使用せず、セキュリティキーやパスキーなどのみでログインします + */ "passwordLessLoginDescription": string; + /** + * パスワードをリセット + */ "resetPassword": string; + /** + * 新しいパスワードは「{password}」です + */ "newPasswordIs": ParameterizedString<"password">; + /** + * UIのアニメーションを減らす + */ "reduceUiAnimation": string; + /** + * 共有 + */ "share": string; + /** + * 見つかりません + */ "notFound": string; + /** + * 指定されたURLに該当するページはありませんでした。 + */ "notFoundDescription": string; + /** + * 既定アップロード先 + */ "uploadFolder": string; + /** + * すべての通知を既読にする + */ "markAsReadAllNotifications": string; + /** + * すべての投稿を既読にする + */ "markAsReadAllUnreadNotes": string; + /** + * すべてのチャットを既読にする + */ "markAsReadAllTalkMessages": string; + /** + * ヘルプ + */ "help": string; + /** + * ここにメッセージを入力 + */ "inputMessageHere": string; + /** + * 閉じる + */ "close": string; + /** + * 招待 + */ "invites": string; + /** + * メンバー + */ "members": string; + /** + * 譲渡 + */ "transfer": string; + /** + * タイトル + */ "title": string; + /** + * テキスト + */ "text": string; + /** + * 有効にする + */ "enable": string; + /** + * 次 + */ "next": string; + /** + * 再入力 + */ "retype": string; + /** + * {user}のノート + */ "noteOf": ParameterizedString<"user">; + /** + * 引用付き + */ "quoteAttached": string; + /** + * 引用として添付しますか? + */ "quoteQuestion": string; + /** + * まだチャットはありません + */ "noMessagesYet": string; + /** + * 新しいメッセージがあります + */ "newMessageExists": string; + /** + * メッセージに添付できるファイルはひとつです + */ "onlyOneFileCanBeAttached": string; + /** + * 続行する前に、サインアップまたはサインインが必要です + */ "signinRequired": string; + /** + * 招待 + */ "invitations": string; + /** + * 招待コード + */ "invitationCode": string; + /** + * 確認しています + */ "checking": string; + /** + * 利用できます + */ "available": string; + /** + * 利用できません + */ "unavailable": string; + /** + * a~z、A~Z、0~9、_が使えます + */ "usernameInvalidFormat": string; + /** + * 短すぎます + */ "tooShort": string; + /** + * 長すぎます + */ "tooLong": string; + /** + * 弱いパスワード + */ "weakPassword": string; + /** + * 普通のパスワード + */ "normalPassword": string; + /** + * 強いパスワード + */ "strongPassword": string; + /** + * 一致しました + */ "passwordMatched": string; + /** + * 一致していません + */ "passwordNotMatched": string; + /** + * {x}でログイン + */ "signinWith": ParameterizedString<"x">; + /** + * ログインできませんでした。ユーザー名とパスワードを確認してください。 + */ "signinFailed": string; + /** + * もしくは + */ "or": string; + /** + * 言語 + */ "language": string; + /** + * UIの表示言語 + */ "uiLanguage": string; + /** + * {x}について + */ "aboutX": ParameterizedString<"x">; + /** + * 絵文字のスタイル + */ "emojiStyle": string; + /** + * ネイティブ + */ "native": string; + /** + * メニューをドロワーで表示しない + */ "disableDrawer": string; + /** + * ノートのアクションをホバー時のみ表示する + */ "showNoteActionsOnlyHover": string; + /** + * 履歴はありません + */ "noHistory": string; + /** + * ログイン履歴 + */ "signinHistory": string; + /** + * 高度なMFMを有効にする + */ "enableAdvancedMfm": string; + /** + * 動きのあるMFMを有効にする + */ "enableAnimatedMfm": string; + /** + * やっています + */ "doing": string; + /** + * カテゴリ + */ "category": string; + /** + * タグ + */ "tags": string; + /** + * このドキュメントのソース + */ "docSource": string; + /** + * アカウントを作成 + */ "createAccount": string; + /** + * 既存のアカウント + */ "existingAccount": string; + /** + * 再生成 + */ "regenerate": string; + /** + * フォントサイズ + */ "fontSize": string; + /** + * 画像が1枚のみのメディアリストの高さ + */ "mediaListWithOneImageAppearance": string; + /** + * {x}を上限に + */ "limitTo": ParameterizedString<"x">; + /** + * フォロー申請はありません + */ "noFollowRequests": string; + /** + * 画像を新しいタブで開く + */ "openImageInNewTab": string; + /** + * ダッシュボード + */ "dashboard": string; + /** + * ローカル + */ "local": string; + /** + * リモート + */ "remote": string; + /** + * 合計 + */ "total": string; + /** + * 前週比 + */ "weekOverWeekChanges": string; + /** + * 前日比 + */ "dayOverDayChanges": string; + /** + * アピアランス + */ "appearance": string; + /** + * クライアント設定 + */ "clientSettings": string; + /** + * アカウント設定 + */ "accountSettings": string; + /** + * プロモーション + */ "promotion": string; + /** + * プロモート + */ "promote": string; + /** + * 日数 + */ "numberOfDays": string; + /** + * このノートを非表示 + */ "hideThisNote": string; + /** + * タイムラインにおすすめのノートを表示する + */ "showFeaturedNotesInTimeline": string; + /** + * オブジェクトストレージ + */ "objectStorage": string; + /** + * オブジェクトストレージを使用 + */ "useObjectStorage": string; + /** + * Base URL + */ "objectStorageBaseUrl": string; + /** + * 参照に使用するURL。CDNやProxyを使用している場合はそのURL、S3: 'https://.s3.amazonaws.com'、GCS等: 'https://storage.googleapis.com/'。 + */ "objectStorageBaseUrlDesc": string; + /** + * Bucket + */ "objectStorageBucket": string; + /** + * 使用サービスのbucket名を指定してください。 + */ "objectStorageBucketDesc": string; + /** + * Prefix + */ "objectStoragePrefix": string; + /** + * このprefixのディレクトリ下に格納されます。 + */ "objectStoragePrefixDesc": string; + /** + * Endpoint + */ "objectStorageEndpoint": string; + /** + * S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。''または':'のように指定します。 + */ "objectStorageEndpointDesc": string; + /** + * Region + */ "objectStorageRegion": string; + /** + * 'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は'us-east-1'にしてください。AWS設定ファイルまたは環境変数を参照する場合は空にしてください。 + */ "objectStorageRegionDesc": string; + /** + * SSLを使用する + */ "objectStorageUseSSL": string; + /** + * API接続にhttpsを使用しない場合はオフにしてください + */ "objectStorageUseSSLDesc": string; + /** + * Proxyを利用する + */ "objectStorageUseProxy": string; + /** + * API接続にproxyを利用しない場合はオフにしてください + */ "objectStorageUseProxyDesc": string; + /** + * アップロード時に'public-read'を設定する + */ "objectStorageSetPublicRead": string; + /** + * s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。 + */ "s3ForcePathStyleDesc": string; + /** + * サーバーログ + */ "serverLogs": string; + /** + * 全て削除 + */ "deleteAll": string; + /** + * タイムライン上部に投稿フォームを表示する + */ "showFixedPostForm": string; + /** + * タイムライン上部に投稿フォームを表示する(チャンネル) + */ "showFixedPostFormInChannel": string; + /** + * フォローする際、デフォルトで返信をTLに含むようにする + */ "withRepliesByDefaultForNewlyFollowed": string; + /** + * 新しいノートがあります + */ "newNoteRecived": string; + /** + * サウンド + */ "sounds": string; + /** + * サウンド + */ "sound": string; + /** + * 聴く + */ "listen": string; + /** + * なし + */ "none": string; + /** + * ページで表示 + */ "showInPage": string; + /** + * ポップアウト + */ "popout": string; + /** + * 音量 + */ "volume": string; + /** + * マスター音量 + */ "masterVolume": string; + /** + * サウンドを出力しない + */ "notUseSound": string; + /** + * Misskeyがアクティブな時のみサウンドを出力する + */ "useSoundOnlyWhenActive": string; + /** + * 詳細 + */ "details": string; + /** + * 絵文字を選択 + */ "chooseEmoji": string; + /** + * 操作を完了できません + */ "unableToProcess": string; + /** + * 最近使用 + */ "recentUsed": string; + /** + * インストール + */ "install": string; + /** + * アンインストール + */ "uninstall": string; + /** + * インストールされたアプリ + */ "installedApps": string; + /** + * ありません + */ "nothing": string; + /** + * インストール日時 + */ "installedDate": string; + /** + * 最終使用日時 + */ "lastUsedDate": string; + /** + * 状態 + */ "state": string; + /** + * ソート + */ "sort": string; + /** + * 昇順 + */ "ascendingOrder": string; + /** + * 降順 + */ "descendingOrder": string; + /** + * スクラッチパッド + */ "scratchpad": string; + /** + * スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。 + */ "scratchpadDescription": string; + /** + * 出力 + */ "output": string; + /** + * スクリプト + */ "script": string; + /** + * Pagesのスクリプトを無効にする + */ "disablePagesScript": string; + /** + * リモートユーザー情報の更新 + */ "updateRemoteUser": string; + /** + * アイコンを解除 + */ "unsetUserAvatar": string; + /** + * アイコンを解除しますか? + */ "unsetUserAvatarConfirm": string; + /** + * バナーを解除 + */ "unsetUserBanner": string; + /** + * バナーを解除しますか? + */ "unsetUserBannerConfirm": string; + /** + * すべてのファイルを削除 + */ "deleteAllFiles": string; + /** + * すべてのファイルを削除しますか? + */ "deleteAllFilesConfirm": string; + /** + * フォローを全解除 + */ "removeAllFollowing": string; + /** + * {host}からのフォローをすべて解除します。そのサーバーがもう存在しなくなった場合などに実行してください。 + */ "removeAllFollowingDescription": ParameterizedString<"host">; + /** + * このユーザーは凍結されています。 + */ "userSuspended": string; + /** + * このユーザーはサイレンスされています。 + */ "userSilenced": string; + /** + * アカウントが凍結されています + */ "yourAccountSuspendedTitle": string; + /** + * このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。 + */ "yourAccountSuspendedDescription": string; + /** + * トークンが無効です + */ "tokenRevoked": string; + /** + * ログイントークンが失効しています。ログインし直してください。 + */ "tokenRevokedDescription": string; + /** + * アカウントは削除されています + */ "accountDeleted": string; + /** + * このアカウントは削除されています。 + */ "accountDeletedDescription": string; + /** + * メニュー + */ "menu": string; + /** + * 分割線 + */ "divider": string; + /** + * 項目を追加 + */ "addItem": string; + /** + * 並び替え + */ "rearrange": string; + /** + * リレー + */ "relays": string; + /** + * リレーの追加 + */ "addRelay": string; + /** + * inboxのURL + */ "inboxUrl": string; + /** + * 追加済みのリレー + */ "addedRelays": string; + /** + * プッシュ通知を行うには有効にする必要があります。 + */ "serviceworkerInfo": string; + /** + * 削除された投稿 + */ "deletedNote": string; + /** + * 非公開の投稿 + */ "invisibleNote": string; + /** + * 自動でもっと見る + */ "enableInfiniteScroll": string; + /** + * 公開範囲 + */ "visibility": string; + /** + * アンケート + */ "poll": string; + /** + * 内容を隠す + */ "useCw": string; + /** + * プレイヤーを開く + */ "enablePlayer": string; + /** + * プレイヤーを閉じる + */ "disablePlayer": string; + /** + * ポストを展開する + */ "expandTweet": string; + /** + * テーマエディター + */ "themeEditor": string; + /** + * 説明 + */ "description": string; + /** + * キャプションを付ける + */ "describeFile": string; + /** + * キャプションを入力 + */ "enterFileDescription": string; + /** + * 作者 + */ "author": string; + /** + * 未保存の変更があります。破棄しますか? + */ "leaveConfirm": string; + /** + * 管理 + */ "manage": string; + /** + * プラグイン + */ "plugins": string; + /** + * 設定のバックアップ + */ "preferencesBackups": string; + /** + * デッキ + */ "deck": string; + /** + * デッキ解除 + */ "undeck": string; + /** + * モーダルにぼかし効果を使用 + */ "useBlurEffectForModal": string; + /** + * フル機能リアクションピッカーを使用 + */ "useFullReactionPicker": string; + /** + * 幅 + */ "width": string; + /** + * 高さ + */ "height": string; + /** + * 大 + */ "large": string; + /** + * 中 + */ "medium": string; + /** + * 小 + */ "small": string; + /** + * アクセストークンの発行 + */ "generateAccessToken": string; + /** + * 権限 + */ "permission": string; + /** + * 管理者権限 + */ "adminPermission": string; + /** + * 全て有効にする + */ "enableAll": string; + /** + * 全て無効にする + */ "disableAll": string; + /** + * アカウントへのアクセス許可 + */ "tokenRequested": string; + /** + * このプラグインはここで設定した権限を行使できるようになります。 + */ "pluginTokenRequestedDescription": string; + /** + * 通知の種類 + */ "notificationType": string; + /** + * 編集 + */ "edit": string; + /** + * メールサーバー + */ "emailServer": string; + /** + * メール配信機能を有効化する + */ "enableEmail": string; + /** + * メールアドレスの確認やパスワードリセットの際に使います + */ "emailConfigInfo": string; + /** + * メール + */ "email": string; + /** + * メールアドレス + */ "emailAddress": string; + /** + * SMTP サーバーの設定 + */ "smtpConfig": string; + /** + * ホスト + */ "smtpHost": string; + /** + * ポート + */ "smtpPort": string; + /** + * ユーザー名 + */ "smtpUser": string; + /** + * パスワード + */ "smtpPass": string; + /** + * ユーザー名とパスワードを空欄にすることで、SMTP認証を無効化出来ます + */ "emptyToDisableSmtpAuth": string; + /** + * SMTP 接続に暗黙的なSSL/TLSを使用する + */ "smtpSecure": string; + /** + * STARTTLS使用時はオフにします。 + */ "smtpSecureInfo": string; + /** + * 配信テスト + */ "testEmail": string; + /** + * ワードミュート + */ "wordMute": string; + /** + * ハードワードミュート + */ "hardWordMute": string; + /** + * 正規表現エラー + */ "regexpError": string; + /** + * {tab}ワードミュートの{line}行目の正規表現にエラーが発生しました: + */ "regexpErrorDescription": ParameterizedString<"tab" | "line">; + /** + * サーバーミュート + */ "instanceMute": string; + /** + * {name}が何かを言いました + */ "userSaysSomething": ParameterizedString<"name">; + /** + * アクティブにする + */ "makeActive": string; + /** + * 表示 + */ "display": string; + /** + * コピー + */ "copy": string; + /** + * メトリクス + */ "metrics": string; + /** + * 概要 + */ "overview": string; + /** + * ログ + */ "logs": string; + /** + * 遅延 + */ "delayed": string; + /** + * データベース + */ "database": string; + /** + * チャンネル + */ "channel": string; + /** + * 作成 + */ "create": string; + /** + * 通知設定 + */ "notificationSetting": string; + /** + * 表示する通知の種別を選択してください。 + */ "notificationSettingDesc": string; + /** + * グローバル設定を使う + */ "useGlobalSetting": string; + /** + * オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。 + */ "useGlobalSettingDesc": string; + /** + * その他 + */ "other": string; + /** + * ログイントークンを再生成 + */ "regenerateLoginToken": string; + /** + * ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。 + */ "regenerateLoginTokenDescription": string; + /** + * カスタム絵文字を検索する時のキーワードになります。 + */ "theKeywordWhenSearchingForCustomEmoji": string; + /** + * スペースで区切って複数設定できます。 + */ "setMultipleBySeparatingWithSpace": string; + /** + * ファイルIDまたはURL + */ "fileIdOrUrl": string; + /** + * 動作 + */ "behavior": string; + /** + * サンプル + */ "sample": string; + /** + * 通報 + */ "abuseReports": string; + /** + * 通報 + */ "reportAbuse": string; + /** + * リノートを通報 + */ "reportAbuseRenote": string; + /** + * {name}を通報する + */ "reportAbuseOf": ParameterizedString<"name">; + /** + * 通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。 + */ "fillAbuseReportDescription": string; + /** + * 内容が送信されました。ご報告ありがとうございました。 + */ "abuseReported": string; + /** + * 通報者 + */ "reporter": string; + /** + * 通報先 + */ "reporteeOrigin": string; + /** + * 通報元 + */ "reporterOrigin": string; + /** + * リモートサーバーに通報を転送する + */ "forwardReport": string; + /** + * リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。 + */ "forwardReportIsAnonymous": string; + /** + * 送信 + */ "send": string; + /** + * 対応済みにする + */ "abuseMarkAsResolved": string; + /** + * 新しいタブで開く + */ "openInNewTab": string; + /** + * サイドビューで開く + */ "openInSideView": string; + /** + * デフォルトのナビゲーション + */ "defaultNavigationBehaviour": string; + /** + * これらの設定を編集するとアカウントが破損する可能性があります。 + */ "editTheseSettingsMayBreakAccount": string; + /** + * ノートのサーバー情報 + */ "instanceTicker": string; + /** + * {x}を待っています + */ "waitingFor": ParameterizedString<"x">; + /** + * ランダム + */ "random": string; + /** + * システム + */ "system": string; + /** + * UI切り替え + */ "switchUi": string; + /** + * デスクトップ + */ "desktop": string; + /** + * クリップ + */ "clip": string; + /** + * 新規作成 + */ "createNew": string; + /** + * 任意 + */ "optional": string; + /** + * 新しいクリップを作成 + */ "createNewClip": string; + /** + * クリップ解除 + */ "unclip": string; + /** + * このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか? + */ "confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">; + /** + * パブリック + */ "public": string; + /** + * 非公開 + */ "private": string; + /** + * Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。 + */ "i18nInfo": ParameterizedString<"link">; + /** + * アクセストークンの管理 + */ "manageAccessTokens": string; + /** + * アカウント情報 + */ "accountInfo": string; + /** + * ノートの数 + */ "notesCount": string; + /** + * 返信した数 + */ "repliesCount": string; + /** + * リノートした数 + */ "renotesCount": string; + /** + * 返信された数 + */ "repliedCount": string; + /** + * リノートされた数 + */ "renotedCount": string; + /** + * フォロー数 + */ "followingCount": string; + /** + * フォロワー数 + */ "followersCount": string; + /** + * リアクションした数 + */ "sentReactionsCount": string; + /** + * リアクションされた数 + */ "receivedReactionsCount": string; + /** + * アンケートに投票した数 + */ "pollVotesCount": string; + /** + * アンケートに投票された数 + */ "pollVotedCount": string; + /** + * はい + */ "yes": string; + /** + * いいえ + */ "no": string; + /** + * ドライブのファイル数 + */ "driveFilesCount": string; + /** + * ドライブ使用量 + */ "driveUsage": string; + /** + * クローラーによるインデックスを拒否 + */ "noCrawle": string; + /** + * 外部の検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要求します。 + */ "noCrawleDescription": string; + /** + * フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。 + */ "lockedAccountInfo": string; + /** + * デフォルトでメディアをセンシティブ設定にする + */ "alwaysMarkSensitive": string; + /** + * 添付画像のサムネイルをオリジナル画質にする + */ "loadRawImages": string; + /** + * アニメーション画像を再生しない + */ "disableShowingAnimatedImages": string; + /** + * メディアがセンシティブであることを分かりやすく表示 + */ "highlightSensitiveMedia": string; + /** + * 確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。 + */ "verificationEmailSent": string; + /** + * 未設定 + */ "notSet": string; + /** + * メールアドレスが確認されました + */ "emailVerified": string; + /** + * お気に入りノートの数 + */ "noteFavoritesCount": string; + /** + * Pageにいいねした数 + */ "pageLikesCount": string; + /** + * Pageにいいねされた数 + */ "pageLikedCount": string; + /** + * 連絡先 + */ "contact": string; + /** + * システムのデフォルトのフォントを使う + */ "useSystemFont": string; + /** + * クリップ + */ "clips": string; + /** + * 実験的機能 + */ "experimentalFeatures": string; + /** + * 実験的 + */ "experimental": string; + /** + * これは実験的な機能です。仕様が変更されたり、正常に動作しなかったりする可能性があります。 + */ "thisIsExperimentalFeature": string; + /** + * 開発者 + */ "developer": string; + /** + * アカウントを見つけやすくする + */ "makeExplorable": string; + /** + * オフにすると、「みつける」にアカウントが載らなくなります。 + */ "makeExplorableDescription": string; + /** + * タイムラインのノートを離して表示 + */ "showGapBetweenNotesInTimeline": string; + /** + * 複製 + */ "duplicate": string; + /** + * 左 + */ "left": string; + /** + * 中央 + */ "center": string; + /** + * 広い + */ "wide": string; + /** + * 狭い + */ "narrow": string; + /** + * 設定はページリロード後に反映されます。今すぐリロードしますか? + */ "reloadToApplySetting": string; + /** + * 反映には再起動が必要です。 + */ "needReloadToApply": string; + /** + * タイトルバーを表示する + */ "showTitlebar": string; + /** + * キャッシュをクリア + */ "clearCache": string; + /** + * {n}人がオンライン + */ "onlineUsersCount": ParameterizedString<"n">; + /** + * {n}ユーザー + */ "nUsers": ParameterizedString<"n">; + /** + * {n}ノート + */ "nNotes": ParameterizedString<"n">; + /** + * エラーリポートを送信 + */ "sendErrorReports": string; + /** + * オンにすると、問題が発生したときにエラーの詳細情報がMisskeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。 + */ "sendErrorReportsDescription": string; + /** + * マイテーマ + */ "myTheme": string; + /** + * 背景 + */ "backgroundColor": string; + /** + * アクセント + */ "accentColor": string; + /** + * 文字 + */ "textColor": string; + /** + * 名前を付けて保存 + */ "saveAs": string; + /** + * 高度 + */ "advanced": string; + /** + * 高度な設定 + */ "advancedSettings": string; + /** + * 値 + */ "value": string; + /** + * 作成日時 + */ "createdAt": string; + /** + * 更新日時 + */ "updatedAt": string; + /** + * 保存しますか? + */ "saveConfirm": string; + /** + * 削除しますか? + */ "deleteConfirm": string; + /** + * 有効な値ではありません。 + */ "invalidValue": string; + /** + * レジストリ + */ "registry": string; + /** + * アカウントを閉鎖する + */ "closeAccount": string; + /** + * 現在のバージョン + */ "currentVersion": string; + /** + * 最新のバージョン + */ "latestVersion": string; + /** + * お使いのクライアントは最新です。 + */ "youAreRunningUpToDateClient": string; + /** + * 新しいバージョンのクライアントが利用可能です。 + */ "newVersionOfClientAvailable": string; + /** + * 使用量 + */ "usageAmount": string; + /** + * 容量 + */ "capacity": string; + /** + * 使用中 + */ "inUse": string; + /** + * コードを編集 + */ "editCode": string; + /** + * 適用 + */ "apply": string; + /** + * サーバーからのお知らせを受け取る + */ "receiveAnnouncementFromInstance": string; + /** + * メール通知 + */ "emailNotification": string; + /** + * 公開 + */ "publish": string; + /** + * チャンネル内検索 + */ "inChannelSearch": string; + /** + * 右クリックでリアクションピッカーを開く + */ "useReactionPickerForContextMenu": string; + /** + * {users}が入力中 + */ "typingUsers": ParameterizedString<"users">; + /** + * 特定の日付にジャンプ + */ "jumpToSpecifiedDate": string; + /** + * 過去のタイムラインを表示しています + */ "showingPastTimeline": string; + /** + * クリア + */ "clear": string; + /** + * 全て既読にする + */ "markAllAsRead": string; + /** + * 戻る + */ "goBack": string; + /** + * いいね解除しますか? + */ "unlikeConfirm": string; + /** + * フルビュー + */ "fullView": string; + /** + * フルビュー解除 + */ "quitFullView": string; + /** + * 説明を追加 + */ "addDescription": string; + /** + * 個々のノートのメニューから「ピン留め」を選択することで、ここにノートを表示しておくことができます。 + */ "userPagePinTip": string; + /** + * 宛先に含まれていないメンションがあります + */ "notSpecifiedMentionWarning": string; + /** + * 情報 + */ "info": string; + /** + * ユーザー情報 + */ "userInfo": string; + /** + * 不明 + */ "unknown": string; + /** + * オンライン状態 + */ "onlineStatus": string; + /** + * オンライン状態を隠す + */ "hideOnlineStatus": string; + /** + * オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。 + */ "hideOnlineStatusDescription": string; + /** + * オンライン + */ "online": string; + /** + * アクティブ + */ "active": string; + /** + * オフライン + */ "offline": string; + /** + * 非推奨 + */ "notRecommended": string; + /** + * Botプロテクション + */ "botProtection": string; + /** + * サーバーブロック・サイレンス + */ "instanceBlocking": string; + /** + * アカウントを選択 + */ "selectAccount": string; + /** + * アカウントを切り替え + */ "switchAccount": string; + /** + * 有効 + */ "enabled": string; + /** + * 無効 + */ "disabled": string; + /** + * クイックアクション + */ "quickAction": string; + /** + * ユーザー + */ "user": string; + /** + * 管理 + */ "administration": string; + /** + * アカウント + */ "accounts": string; + /** + * 切り替え + */ "switch": string; + /** + * 管理者情報が設定されていません。 + */ "noMaintainerInformationWarning": string; + /** + * Botプロテクションが設定されていません。 + */ "noBotProtectionWarning": string; + /** + * 設定する + */ "configure": string; + /** + * ギャラリーへ投稿 + */ "postToGallery": string; + /** + * このハッシュタグで投稿 + */ "postToHashtag": string; + /** + * ギャラリー + */ "gallery": string; + /** + * 最近の投稿 + */ "recentPosts": string; + /** + * 人気の投稿 + */ "popularPosts": string; + /** + * ノートで共有 + */ "shareWithNote": string; + /** + * 広告 + */ "ads": string; + /** + * 期限 + */ "expiration": string; + /** + * 開始期間 + */ "startingperiod": string; + /** + * メモ + */ "memo": string; + /** + * 優先度 + */ "priority": string; + /** + * 高 + */ "high": string; + /** + * 中 + */ "middle": string; + /** + * 低 + */ "low": string; + /** + * メールアドレスの設定がされていません。 + */ "emailNotConfiguredWarning": string; + /** + * 比率 + */ "ratio": string; + /** + * 本文をプレビュー + */ "previewNoteText": string; + /** + * カスタムCSS + */ "customCss": string; + /** + * この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。 + */ "customCssWarn": string; + /** + * グローバル + */ "global": string; + /** + * アイコンを四角形で表示 + */ "squareAvatars": string; + /** + * 送信 + */ "sent": string; + /** + * 受信 + */ "received": string; + /** + * 検索結果 + */ "searchResult": string; + /** + * ハッシュタグ + */ "hashtags": string; + /** + * トラブルシューティング + */ "troubleshooting": string; + /** + * UIにぼかし効果を使用 + */ "useBlurEffect": string; + /** + * 詳しく + */ "learnMore": string; + /** + * Misskeyが更新されました! + */ "misskeyUpdated": string; + /** + * 更新情報を見る + */ "whatIsNew": string; + /** + * 翻訳 + */ "translate": string; + /** + * {x}から翻訳 + */ "translatedFrom": ParameterizedString<"x">; + /** + * アカウントの削除が進行中です + */ "accountDeletionInProgress": string; + /** + * サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。 + */ "usernameInfo": string; + /** + * 藍モード + */ "aiChanMode": string; + /** + * 開発者モード + */ "devMode": string; + /** + * CWを維持する + */ "keepCw": string; + /** + * Pub/Subのアカウント + */ "pubSub": string; + /** + * 直近の通信 + */ "lastCommunication": string; + /** + * 解決済み + */ "resolved": string; + /** + * 未解決 + */ "unresolved": string; + /** + * フォロワーを解除 + */ "breakFollow": string; + /** + * フォロワー解除しますか? + */ "breakFollowConfirm": string; + /** + * オンになっています + */ "itsOn": string; + /** + * オフになっています + */ "itsOff": string; + /** + * オン + */ "on": string; + /** + * オフ + */ "off": string; + /** + * アカウント登録にメールアドレスを必須にする + */ "emailRequiredForSignup": string; + /** + * 未読 + */ "unread": string; + /** + * フィルタ + */ "filter": string; + /** + * コントロールパネル + */ "controlPanel": string; + /** + * アカウントを管理 + */ "manageAccounts": string; + /** + * リアクション一覧を公開する + */ "makeReactionsPublic": string; + /** + * あなたがしたリアクション一覧を誰でも見れるようにします。 + */ "makeReactionsPublicDescription": string; + /** + * クラシック + */ "classic": string; + /** + * スレッドをミュート + */ "muteThread": string; + /** + * スレッドのミュートを解除 + */ "unmuteThread": string; + /** + * フォローの公開範囲 + */ "followingVisibility": string; + /** + * フォロワーの公開範囲 + */ "followersVisibility": string; + /** + * さらにスレッドを見る + */ "continueThread": string; + /** + * アカウントが削除されます。よろしいですか? + */ "deleteAccountConfirm": string; + /** + * パスワードが間違っています。 + */ "incorrectPassword": string; + /** + * 「{choice}」に投票しますか? + */ "voteConfirm": ParameterizedString<"choice">; + /** + * 隠す + */ "hide": string; + /** + * モバイルデバイスのときドロワーで表示 + */ "useDrawerReactionPickerForMobile": string; + /** + * おかえりなさい、{name}さん + */ "welcomeBackWithName": ParameterizedString<"name">; + /** + * [{ok}]を押して、メールアドレスの確認を完了してください。 + */ "clickToFinishEmailVerification": ParameterizedString<"ok">; + /** + * デバイスタイプ + */ "overridedDeviceKind": string; + /** + * スマートフォン + */ "smartphone": string; + /** + * タブレット + */ "tablet": string; + /** + * 自動 + */ "auto": string; + /** + * テーマカラー + */ "themeColor": string; + /** + * サイズ + */ "size": string; + /** + * 列の数 + */ "numberOfColumn": string; + /** + * 検索 + */ "searchByGoogle": string; + /** + * サーバーデフォルトのライトテーマ + */ "instanceDefaultLightTheme": string; + /** + * サーバーデフォルトのダークテーマ + */ "instanceDefaultDarkTheme": string; + /** + * オブジェクト形式のテーマコードを記入します。 + */ "instanceDefaultThemeDescription": string; + /** + * ミュートする期限 + */ "mutePeriod": string; + /** + * 期限 + */ "period": string; + /** + * 無期限 + */ "indefinitely": string; + /** + * 10分 + */ "tenMinutes": string; + /** + * 1時間 + */ "oneHour": string; + /** + * 1日 + */ "oneDay": string; + /** + * 1週間 + */ "oneWeek": string; + /** + * 1ヶ月 + */ "oneMonth": string; + /** + * 反映されるまで時間がかかる場合があります。 + */ "reflectMayTakeTime": string; + /** + * アカウント情報の取得に失敗しました + */ "failedToFetchAccountInformation": string; + /** + * レート制限を超えました + */ "rateLimitExceeded": string; + /** + * 画像のクロップ + */ "cropImage": string; + /** + * 画像をクロップしますか? + */ "cropImageAsk": string; + /** + * クロップする + */ "cropYes": string; + /** + * そのまま使う + */ "cropNo": string; + /** + * ファイル + */ "file": string; + /** + * 直近{n}時間 + */ "recentNHours": ParameterizedString<"n">; + /** + * 直近{n}日 + */ "recentNDays": ParameterizedString<"n">; + /** + * メールサーバーの設定がされていません。 + */ "noEmailServerWarning": string; + /** + * 未対応の通報があります。 + */ "thereIsUnresolvedAbuseReportWarning": string; + /** + * 推奨 + */ "recommended": string; + /** + * チェック + */ "check": string; + /** + * このユーザーのドライブ容量上限を変更 + */ "driveCapOverrideLabel": string; + /** + * 0以下を指定すると解除されます。 + */ "driveCapOverrideCaption": string; + /** + * 閲覧するには管理者アカウントでログインしている必要があります。 + */ "requireAdminForView": string; + /** + * システムにより自動で作成・管理されているアカウントです。 + */ "isSystemAccount": string; + /** + * この操作を行うには {x} と入力してください + */ "typeToConfirm": ParameterizedString<"x">; + /** + * アカウント削除 + */ "deleteAccount": string; + /** + * ドキュメント + */ "document": string; + /** + * ページキャッシュ数 + */ "numberOfPageCache": string; + /** + * 多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。 + */ "numberOfPageCacheDescription": string; + /** + * ログアウトしますか? + */ "logoutConfirm": string; + /** + * 最終利用日時 + */ "lastActiveDate": string; + /** + * ステータスバー + */ "statusbar": string; + /** + * 選択してください + */ "pleaseSelect": string; + /** + * 反転 + */ "reverse": string; + /** + * 色付き + */ "colored": string; + /** + * 更新間隔 + */ "refreshInterval": string; + /** + * ラベル + */ "label": string; + /** + * タイプ + */ "type": string; + /** + * 速度 + */ "speed": string; + /** + * 遅い + */ "slow": string; + /** + * 速い + */ "fast": string; + /** + * センシティブなメディアの検出 + */ "sensitiveMediaDetection": string; + /** + * ローカルのみ + */ "localOnly": string; + /** + * リモートのみ + */ "remoteOnly": string; + /** + * アップロード失敗 + */ "failedToUpload": string; + /** + * 不適切な内容を含む可能性があると判定されたためアップロードできません。 + */ "cannotUploadBecauseInappropriate": string; + /** + * ドライブの空き容量が無いためアップロードできません。 + */ "cannotUploadBecauseNoFreeSpace": string; + /** + * ファイルサイズの制限を超えているためアップロードできません。 + */ "cannotUploadBecauseExceedsFileSizeLimit": string; + /** + * ベータ + */ "beta": string; + /** + * 自動センシティブ判定 + */ "enableAutoSensitive": string; + /** + * 利用可能な場合は、機械学習を利用して自動でメディアにセンシティブフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。 + */ "enableAutoSensitiveDescription": string; + /** + * ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。 + */ "activeEmailValidationDescription": string; + /** + * ナビゲーションバー + */ "navbar": string; + /** + * シャッフル + */ "shuffle": string; + /** + * アカウント + */ "account": string; + /** + * 移動 + */ "move": string; + /** + * プッシュ通知 + */ "pushNotification": string; + /** + * プッシュ通知を有効化 + */ "subscribePushNotification": string; + /** + * プッシュ通知を停止する + */ "unsubscribePushNotification": string; + /** + * プッシュ通知は有効です + */ "pushNotificationAlreadySubscribed": string; + /** + * ブラウザかサーバーがプッシュ通知に非対応 + */ "pushNotificationNotSupported": string; + /** + * 通知が既読になったらプッシュ通知を削除する + */ "sendPushNotificationReadMessage": string; + /** + * 端末の電池消費量が増加する可能性があります。 + */ "sendPushNotificationReadMessageCaption": string; + /** + * 最大化 + */ "windowMaximize": string; + /** + * 最小化 + */ "windowMinimize": string; + /** + * 元に戻す + */ "windowRestore": string; + /** + * キャプション + */ "caption": string; + /** + * Botアカウントでログイン中 + */ "loggedInAsBot": string; + /** + * ツール + */ "tools": string; + /** + * 読み込めません + */ "cannotLoad": string; + /** + * プロフィール表示回数 + */ "numberOfProfileView": string; + /** + * いいね! + */ "like": string; + /** + * いいねを解除 + */ "unlike": string; + /** + * いいね数 + */ "numberOfLikes": string; + /** + * 表示 + */ "show": string; + /** + * 今後表示しない + */ "neverShow": string; + /** + * また後で + */ "remindMeLater": string; + /** + * Misskeyを気に入っていただけましたか? + */ "didYouLikeMisskey": string; + /** + * Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします! + */ "pleaseDonate": ParameterizedString<"host">; + /** + * ロール + */ "roles": string; + /** + * ロール + */ "role": string; + /** + * ロールはありません + */ "noRole": string; + /** + * 一般ユーザー + */ "normalUser": string; + /** + * 未定義 + */ "undefined": string; + /** + * アサイン + */ "assign": string; + /** + * アサインを解除 + */ "unassign": string; + /** + * 色 + */ "color": string; + /** + * カスタム絵文字の管理 + */ "manageCustomEmojis": string; + /** + * アバターデコレーションの管理 + */ "manageAvatarDecorations": string; + /** + * これ以上作成することはできません。 + */ "youCannotCreateAnymore": string; + /** + * 一時的に利用できません + */ "cannotPerformTemporary": string; + /** + * 操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。 + */ "cannotPerformTemporaryDescription": string; + /** + * パラメータエラー + */ "invalidParamError": string; + /** + * リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる等の可能性もあります。 + */ "invalidParamErrorDescription": string; + /** + * 操作が拒否されました + */ "permissionDeniedError": string; + /** + * このアカウントにはこの操作を行うための権限がありません。 + */ "permissionDeniedErrorDescription": string; + /** + * プリセット + */ "preset": string; + /** + * プリセットから選択 + */ "selectFromPresets": string; + /** + * 実績 + */ "achievements": string; + /** + * サーバーの応答が無効です + */ "gotInvalidResponseError": string; + /** + * サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。 + */ "gotInvalidResponseErrorDescription": string; + /** + * この投稿は迷惑になる可能性があります。 + */ "thisPostMayBeAnnoying": string; + /** + * ホームに投稿 + */ "thisPostMayBeAnnoyingHome": string; + /** + * やめる + */ "thisPostMayBeAnnoyingCancel": string; + /** + * このまま投稿 + */ "thisPostMayBeAnnoyingIgnore": string; + /** + * 見たことのあるリノートを省略して表示 + */ "collapseRenotes": string; + /** + * サーバー内部エラー + */ "internalServerError": string; + /** + * サーバー内部で予期しないエラーが発生しました。 + */ "internalServerErrorDescription": string; + /** + * エラー情報をコピー + */ "copyErrorInfo": string; + /** + * このサーバーに登録する + */ "joinThisServer": string; + /** + * 他のサーバーを探す + */ "exploreOtherServers": string; + /** + * タイムラインを見てみる + */ "letsLookAtTimeline": string; + /** + * 連合なしにしますか? + */ "disableFederationConfirm": string; + /** + * 連合なしにしても投稿は非公開になりません。ほとんどの場合、連合なしにする必要はありません。 + */ "disableFederationConfirmWarn": string; + /** + * 連合なしにする + */ "disableFederationOk": string; + /** + * 現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。 + */ "invitationRequiredToRegister": string; + /** + * このサーバーではメール配信はサポートされていません + */ "emailNotSupported": string; + /** + * チャンネルに投稿 + */ "postToTheChannel": string; + /** + * 後から変更できません。 + */ "cannotBeChangedLater": string; + /** + * リアクションの受け入れ + */ "reactionAcceptance": string; + /** + * いいねのみ + */ "likeOnly": string; + /** + * 全て (リモートはいいねのみ) + */ "likeOnlyForRemote": string; + /** + * 非センシティブのみ + */ "nonSensitiveOnly": string; + /** + * 非センシティブのみ (リモートはいいねのみ) + */ "nonSensitiveOnlyForLocalLikeOnlyForRemote": string; + /** + * 自分に割り当てられたロール + */ "rolesAssignedToMe": string; + /** + * パスワードリセットしますか? + */ "resetPasswordConfirm": string; + /** + * センシティブワード + */ "sensitiveWords": string; + /** + * 設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。 + */ "sensitiveWordsDescription": string; + /** + * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。 + */ "sensitiveWordsDescription2": string; + /** + * 非表示ハッシュタグ + */ "hiddenTags": string; + /** + * 設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。 + */ "hiddenTagsDescription": string; + /** + * ノート検索は利用できません。 + */ "notesSearchNotAvailable": string; + /** + * ライセンス + */ "license": string; + /** + * お気に入り解除しますか? + */ "unfavoriteConfirm": string; + /** + * 自分のクリップ + */ "myClips": string; + /** + * ドライブクリーナー + */ "drivecleaner": string; + /** + * すべてのキューを今すぐ再試行 + */ "retryAllQueuesNow": string; + /** + * 今すぐ再試行しますか? + */ "retryAllQueuesConfirmTitle": string; + /** + * 一時的にサーバーの負荷が増大することがあります。 + */ "retryAllQueuesConfirmText": string; + /** + * リモートユーザーのチャートを生成 + */ "enableChartsForRemoteUser": string; + /** + * リモートサーバーのチャートを生成 + */ "enableChartsForFederatedInstances": string; + /** + * ノートのアクションにクリップを追加 + */ "showClipButtonInNoteFooter": string; + /** + * リアクションの表示サイズ + */ "reactionsDisplaySize": string; + /** + * リアクションの最大横幅を制限し、縮小して表示する + */ "limitWidthOfReaction": string; + /** + * ノートIDまたはURL + */ "noteIdOrUrl": string; + /** + * 動画 + */ "video": string; + /** + * 動画 + */ "videos": string; + /** + * 音声 + */ "audio": string; + /** + * 音声 + */ "audioFiles": string; + /** + * データセーバー + */ "dataSaver": string; + /** + * アカウントの移行 + */ "accountMigration": string; + /** + * このユーザーは新しいアカウントに移行しました: + */ "accountMoved": string; + /** + * このアカウントは移行されています + */ "accountMovedShort": string; + /** + * この操作はできません + */ "operationForbidden": string; + /** + * 常に広告を表示する + */ "forceShowAds": string; + /** + * メモを追加 + */ "addMemo": string; + /** + * メモを編集 + */ "editMemo": string; + /** + * リアクション一覧 + */ "reactionsList": string; + /** + * リノート一覧 + */ "renotesList": string; + /** + * 通知の表示 + */ "notificationDisplay": string; + /** + * 左上 + */ "leftTop": string; + /** + * 右上 + */ "rightTop": string; + /** + * 左下 + */ "leftBottom": string; + /** + * 右下 + */ "rightBottom": string; + /** + * スタック方向 + */ "stackAxis": string; + /** + * 縦 + */ "vertical": string; + /** + * 横 + */ "horizontal": string; + /** + * 位置 + */ "position": string; + /** + * サーバールール + */ "serverRules": string; + /** + * このサーバーに登録するには、以下の内容を確認し同意する必要があります。 + */ "pleaseConfirmBelowBeforeSignup": string; + /** + * 続けるには、全ての「同意する」にチェックが入っている必要があります。 + */ "pleaseAgreeAllToContinue": string; + /** + * 続ける + */ "continue": string; + /** + * 予約ユーザー名 + */ "preservedUsernames": string; + /** + * 予約するユーザー名を改行で列挙します。ここで指定されたユーザー名はアカウント作成時に使えなくなりますが、管理者によるアカウント作成時はこの制限を受けません。また、既に存在するアカウントも影響を受けません。 + */ "preservedUsernamesDescription": string; + /** + * このファイルからノートを作成 + */ "createNoteFromTheFile": string; + /** + * アーカイブ + */ "archive": string; + /** + * {name}をアーカイブしますか? + */ "channelArchiveConfirmTitle": ParameterizedString<"name">; + /** + * アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。 + */ "channelArchiveConfirmDescription": string; + /** + * このチャンネルはアーカイブされています。 + */ "thisChannelArchived": string; + /** + * ノートの表示 + */ "displayOfNote": string; + /** + * 初期設定 + */ "initialAccountSetting": string; + /** + * フォロー中 + */ "youFollowing": string; + /** + * 生成AIによる学習を拒否 + */ "preventAiLearning": string; + /** + * 外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。 + */ "preventAiLearningDescription": string; + /** + * オプション + */ "options": string; + /** + * ユーザー指定 + */ "specifyUser": string; + /** + * プレビューできません + */ "failedToPreviewUrl": string; + /** + * 更新 + */ "update": string; + /** + * リアクションとして使えるロール + */ "rolesThatCanBeUsedThisEmojiAsReaction": string; + /** + * ロールの指定が一つもない場合、誰でもリアクションとして使えます。 + */ "rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription": string; + /** + * ロールは公開ロールである必要があります。 + */ "rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn": string; + /** + * リアクションを取り消しますか? + */ "cancelReactionConfirm": string; + /** + * リアクションを変更しますか? + */ "changeReactionConfirm": string; + /** + * あとで + */ "later": string; + /** + * Misskeyへ + */ "goToMisskey": string; + /** + * 絵文字の追加辞書 + */ "additionalEmojiDictionary": string; + /** + * インストール済み + */ "installed": string; + /** + * ブランディング + */ "branding": string; + /** + * サーバーのマシン情報を公開する + */ "enableServerMachineStats": string; + /** + * ユーザーごとのIdenticon生成を有効にする + */ "enableIdenticonGeneration": string; + /** + * オフにするとパフォーマンスが向上します。 + */ "turnOffToImprovePerformance": string; + /** + * 招待コードを作成 + */ "createInviteCode": string; + /** + * オプションを指定して作成 + */ "createWithOptions": string; + /** + * 作成数 + */ "createCount": string; + /** + * 招待コードを作成しました + */ "inviteCodeCreated": string; + /** + * 作成できる招待コードの数が上限に達しています。 + */ "inviteLimitExceeded": string; + /** + * 作成できる招待コード: 残り {limit} 個 + */ "createLimitRemaining": ParameterizedString<"limit">; + /** + * {time}で最大 {limit} 個の招待コードを作成できます。 + */ "inviteLimitResetCycle": ParameterizedString<"time" | "limit">; + /** + * 有効期限 + */ "expirationDate": string; + /** + * 有効期限を設けない + */ "noExpirationDate": string; + /** + * 招待コードが使用された日時 + */ "inviteCodeUsedAt": string; + /** + * 招待コードを使用したユーザー + */ "registeredUserUsingInviteCode": string; + /** + * メール認証待ち + */ "waitingForMailAuth": string; + /** + * 招待コードを作成したユーザー + */ "inviteCodeCreator": string; + /** + * 使用日時 + */ "usedAt": string; + /** + * 未使用 + */ "unused": string; + /** + * 使用済み + */ "used": string; + /** + * 期限切れ + */ "expired": string; + /** + * 同意しますか? + */ "doYouAgree": string; + /** + * 重要ですので必ずお読みください。 + */ "beSureToReadThisAsItIsImportant": string; + /** + * 「{x}」の内容をよく読み、同意します。 + */ "iHaveReadXCarefullyAndAgree": ParameterizedString<"x">; + /** + * ダイアログ + */ "dialog": string; + /** + * アイコン + */ "icon": string; + /** + * あなたへ + */ "forYou": string; + /** + * 現在のお知らせ + */ "currentAnnouncements": string; + /** + * 過去のお知らせ + */ "pastAnnouncements": string; + /** + * 未読のお知らせがあります。 + */ "youHaveUnreadAnnouncements": string; + /** + * ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。 + */ "useSecurityKey": string; + /** + * 返信 + */ "replies": string; + /** + * リノート + */ "renotes": string; + /** + * 返信を見る + */ "loadReplies": string; + /** + * 会話を見る + */ "loadConversation": string; + /** + * ピン留めされたリスト + */ "pinnedList": string; + /** + * デバイスの画面を常にオンにする + */ "keepScreenOn": string; + /** + * このリンク先の所有者であることが確認されました + */ "verifiedLink": string; + /** + * 投稿を通知 + */ "notifyNotes": string; + /** + * 投稿の通知を解除 + */ "unnotifyNotes": string; + /** + * 認証 + */ "authentication": string; + /** + * 続けるには認証を行ってください + */ "authenticationRequiredToContinue": string; + /** + * 日時 + */ "dateAndTime": string; + /** + * リノートを表示 + */ "showRenotes": string; + /** + * 編集済み + */ "edited": string; + /** + * 通知の受信設定 + */ "notificationRecieveConfig": string; + /** + * 相互フォロー + */ "mutualFollow": string; + /** + * ファイル付きのみ + */ "fileAttachedOnly": string; + /** + * TLに他の人への返信を含める + */ "showRepliesToOthersInTimeline": string; + /** + * TLに他の人への返信を含めない + */ "hideRepliesToOthersInTimeline": string; + /** + * TLに現在フォロー中の人全員の返信を含めるようにする + */ "showRepliesToOthersInTimelineAll": string; + /** + * TLに現在フォロー中の人全員の返信を含めないようにする + */ "hideRepliesToOthersInTimelineAll": string; + /** + * この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか? + */ "confirmShowRepliesAll": string; + /** + * この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか? + */ "confirmHideRepliesAll": string; + /** + * 外部サービス + */ "externalServices": string; + /** + * 運営者情報 + */ "impressum": string; + /** + * 運営者情報URL + */ "impressumUrl": string; + /** + * ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。 + */ "impressumDescription": string; + /** + * プライバシーポリシー + */ "privacyPolicy": string; + /** + * プライバシーポリシーURL + */ "privacyPolicyUrl": string; + /** + * 利用規約・プライバシーポリシー + */ "tosAndPrivacyPolicy": string; + /** + * アイコンデコレーション + */ "avatarDecorations": string; + /** + * 付ける + */ "attach": string; + /** + * 外す + */ "detach": string; + /** + * 全て外す + */ "detachAll": string; + /** + * 角度 + */ "angle": string; + /** + * 反転 + */ "flip": string; + /** + * アイコンのデコレーションを表示 + */ "showAvatarDecorations": string; + /** + * 離してリロード + */ "releaseToRefresh": string; + /** + * リロード中 + */ "refreshing": string; + /** + * 引っ張ってリロード + */ "pullDownToRefresh": string; + /** + * タイムラインのリアルタイム更新を無効にする + */ "disableStreamingTimeline": string; + /** + * 通知をグルーピングして表示する + */ "useGroupedNotifications": string; + /** + * メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。 + */ "signupPendingError": string; + /** + * 「内容を隠す」がオンの場合は注釈の記述が必要です。 + */ "cwNotationRequired": string; + /** + * リアクションする + */ "doReaction": string; + /** + * コード + */ "code": string; + /** + * 設定の反映にはリロードが必要です。 + */ "reloadRequiredToApplySettings": string; + /** + * 残り: {n} + */ "remainingN": ParameterizedString<"n">; + /** + * 現在の内容に上書きされますがよろしいですか? + */ "overwriteContentConfirm": string; + /** + * 季節に応じた画面の演出 + */ "seasonalScreenEffect": string; + /** + * デコる + */ "decorate": string; + /** + * 装飾を追加 + */ "addMfmFunction": string; + /** + * 高度なMFMのピッカーを表示する + */ "enableQuickAddMfmFunction": string; + /** + * バブルゲーム + */ "bubbleGame": string; + /** + * 効果音 + */ "sfx": string; + /** + * サウンドが再生されます + */ "soundWillBePlayed": string; + /** + * リプレイを見る + */ "showReplay": string; + /** + * リプレイ + */ "replay": string; + /** + * リプレイ中 + */ "replaying": string; + /** + * ランキング + */ "ranking": string; + /** + * 直近{n}日 + */ "lastNDays": ParameterizedString<"n">; + /** + * タイトルへ + */ "backToTitle": string; + /** + * スワイプしてタブを切り替える + */ "enableHorizontalSwipe": string; "_bubbleGame": { + /** + * 遊び方 + */ "howToPlay": string; "_howToPlay": { + /** + * 位置を調整してハコにモノを落とします。 + */ "section1": string; + /** + * 同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。 + */ "section2": string; + /** + * モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう! + */ "section3": string; }; }; "_announcement": { + /** + * 既存ユーザーのみ + */ "forExistingUsers": string; + /** + * 有効にすると、このお知らせ作成時点で存在するユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。 + */ "forExistingUsersDescription": string; + /** + * 既読にするのに確認が必要 + */ "needConfirmationToRead": string; + /** + * 有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。また、一括既読操作の対象になりません。 + */ "needConfirmationToReadDescription": string; + /** + * お知らせを終了 + */ "end": string; + /** + * アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。 + */ "tooManyActiveAnnouncementDescription": string; + /** + * 既読にしますか? + */ "readConfirmTitle": string; + /** + * 「{title}」の内容を読み、既読にします。 + */ "readConfirmText": ParameterizedString<"title">; + /** + * 特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。 + */ "shouldNotBeUsedToPresentPermanentInfo": string; + /** + * ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。 + */ "dialogAnnouncementUxWarn": string; + /** + * 非通知 + */ "silence": string; + /** + * オンにすると、このお知らせは通知されず、既読にする必要もなくなります。 + */ "silenceDescription": string; }; "_initialAccountSetting": { + /** + * アカウントの作成が完了しました! + */ "accountCreated": string; + /** + * さっそくアカウントの初期設定を行いましょう。 + */ "letsStartAccountSetup": string; + /** + * まずはあなたのプロフィールを設定しましょう。 + */ "letsFillYourProfile": string; + /** + * プロフィール設定 + */ "profileSetting": string; + /** + * プライバシー設定 + */ "privacySetting": string; + /** + * これらの設定は後から変更できます。 + */ "theseSettingsCanEditLater": string; + /** + * この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。 + */ "youCanEditMoreSettingsInSettingsPageLater": string; + /** + * タイムラインを構築するため、気になるユーザーをフォローしてみましょう。 + */ "followUsers": string; + /** + * プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。 + */ "pushNotificationDescription": ParameterizedString<"name">; + /** + * 初期設定が完了しました! + */ "initialAccountSettingCompleted": string; + /** + * {name}をお楽しみください! + */ "haveFun": ParameterizedString<"name">; + /** + * このまま{name}(Misskey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。 + */ "youCanContinueTutorial": ParameterizedString<"name">; + /** + * チュートリアルを開始 + */ "startTutorial": string; + /** + * 初期設定をスキップしますか? + */ "skipAreYouSure": string; + /** + * 初期設定をあとでやり直しますか? + */ "laterAreYouSure": string; }; "_initialTutorial": { + /** + * チュートリアルを見る + */ "launchTutorial": string; + /** + * チュートリアル + */ "title": string; + /** + * よくできました + */ "wellDone": string; + /** + * チュートリアルを終了しますか? + */ "skipAreYouSure": string; "_landing": { + /** + * チュートリアルへようこそ + */ "title": string; + /** + * ここでは、Misskeyの基本的な使い方や機能を確認できます。 + */ "description": string; }; "_note": { + /** + * ノートって何? + */ "title": string; + /** + * Misskeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。 + */ "description": string; + /** + * 返信することができます。返信に対しての返信も可能で、スレッドのように会話を続けることもできます。 + */ "reply": string; + /** + * そのノートを自分のタイムラインに流して共有することができます。テキストを追加して引用することも可能です。 + */ "renote": string; + /** + * リアクションをつけることができます。詳しくは次のページで解説します。 + */ "reaction": string; + /** + * ノートの詳細を表示したり、リンクをコピーしたりなどの様々な操作が行えます。 + */ "menu": string; }; "_reaction": { + /** + * リアクションって何? + */ "title": string; + /** + * ノートには「リアクション」をつけることができます。「いいね」では伝わらないニュアンスも、リアクションで簡単・気軽に表現できます。 + */ "description": string; + /** + * リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください! + */ "letsTryReacting": string; + /** + * リアクションをつけると先に進めるようになります。 + */ "reactToContinue": string; + /** + * あなたのノートが誰かにリアクションされると、リアルタイムで通知を受け取ります。 + */ "reactNotification": string; + /** + * 「ー」ボタンを押すとリアクションを取り消すことができます。 + */ "reactDone": string; }; "_timeline": { + /** + * タイムラインのしくみ + */ "title": string; + /** + * Misskeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。 + */ "description1": string; + /** + * あなたがフォローしているアカウントの投稿を見られます。 + */ "home": string; + /** + * このサーバーにいるユーザー全員の投稿を見られます。 + */ "local": string; + /** + * ホームタイムラインとローカルタイムラインの投稿が両方表示されます。 + */ "social": string; + /** + * 接続している他のすべてのサーバーからの投稿を見られます。 + */ "global": string; + /** + * それぞれのタイムラインは、画面上部でいつでも切り替えられます。 + */ "description2": string; + /** + * その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。 + */ "description3": ParameterizedString<"link">; }; "_postNote": { + /** + * ノートの投稿設定 + */ "title": string; + /** + * Misskeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。 + */ "description1": string; "_visibility": { + /** + * ノートを表示できる相手を制限できます。 + */ "description": string; + /** + * すべてのユーザーに公開。 + */ "public": string; + /** + * ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。 + */ "home": string; + /** + * フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。 + */ "followers": string; + /** + * 指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。 + */ "direct": string; + /** + * 機密情報は送信する際は注意してください。 + */ "doNotSendConfidencialOnDirect1": string; + /** + * 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。 + */ "doNotSendConfidencialOnDirect2": string; + /** + * 他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。 + */ "localOnly": string; }; "_cw": { + /** + * 内容を隠す(CW) + */ "title": string; + /** + * 本文のかわりに「注釈」に書いた内容が表示されます。「もっと見る」を押すと本文が表示されます。 + */ "description": string; "_exampleNote": { + /** + * 飯テロ注意 + */ "cw": string; + /** + * チョコのかかったドーナツを食べました🍩😋 + */ "note": string; }; + /** + * サーバーのガイドラインにより必要とされるノートに指定したり、ネタバレ投稿やセンシティブな文章を自主規制したりするときに使います。 + */ "useCases": string; }; }; "_howToMakeAttachmentsSensitive": { + /** + * 添付ファイルをセンシティブにするには? + */ "title": string; + /** + * サーバーのガイドラインにより必要とされる際や、そのまま見れる状態にしておくべきではない添付ファイルには、「センシティブ」設定を付けます。 + */ "description": string; + /** + * 試しに、このフォームに添付された画像をセンシティブにしてみてください! + */ "tryThisFile": string; "_exampleNote": { + /** + * 納豆のフタ開けるのミスったわね… + */ "note": string; }; + /** + * 添付ファイルをセンシティブにする際は、そのファイルをクリックしてメニューを開き、「センシティブとして設定」をクリックします。 + */ "method": string; + /** + * ファイルを添付する際は、サーバーのガイドラインに従ってセンシティブを適切に設定してください。 + */ "sensitiveSucceeded": string; + /** + * 画像をセンシティブに設定すると先に進めるようになります。 + */ "doItToContinue": string; }; "_done": { + /** + * チュートリアルは終了です🎉 + */ "title": string; + /** + * ここで紹介した機能はほんの一部にすぎません。Misskeyの使い方をより詳しく知るには、{link}をご覧ください。 + */ "description": ParameterizedString<"link">; }; }; "_timelineDescription": { + /** + * ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。 + */ "home": string; + /** + * ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。 + */ "local": string; + /** + * ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。 + */ "social": string; + /** + * グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。 + */ "global": string; }; "_serverRules": { + /** + * 新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。 + */ "description": string; }; "_serverSettings": { + /** + * アイコン画像のURL + */ "iconUrl": string; + /** + * {host}がアプリとして表示される際のアイコンを指定します。 + */ "appIconDescription": ParameterizedString<"host">; + /** + * 例: PWAや、スマートフォンのホーム画面にブックマークとして追加された時など + */ "appIconUsageExample": string; + /** + * 円形もしくは角丸にクロップされる場合があるため、塗り潰された余白のある背景を持つことが推奨されます。 + */ "appIconStyleRecommendation": string; + /** + * 解像度は必ず{resolution}である必要があります。 + */ "appIconResolutionMustBe": ParameterizedString<"resolution">; + /** + * manifest.jsonのオーバーライド + */ "manifestJsonOverride": string; + /** + * 略称 + */ "shortName": string; + /** + * サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。 + */ "shortNameDescription": string; + /** + * 有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。 + */ "fanoutTimelineDescription": string; + /** + * データベースへのフォールバック + */ "fanoutTimelineDbFallback": string; + /** + * 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。 + */ "fanoutTimelineDbFallbackDescription": string; }; "_accountMigration": { + /** + * 別のアカウントからこのアカウントに移行 + */ "moveFrom": string; + /** + * 別のアカウントへエイリアスを作成 + */ "moveFromSub": string; + /** + * 移行元のアカウント #{n} + */ "moveFromLabel": ParameterizedString<"n">; + /** + * 別のアカウントからこのアカウントに移行したい場合、ここでエイリアスを作成しておく必要があります。 + * 移行元のアカウントをこのように入力してください: @username@server.example.com + * 削除するには、入力欄を空にして保存します(非推奨)。 + */ "moveFromDescription": string; + /** + * このアカウントを新しいアカウントへ移行 + */ "moveTo": string; + /** + * 移行先のアカウント: + */ "moveToLabel": string; + /** + * アカウントを移行すると、取り消すことはできません。 + */ "moveCannotBeUndone": string; + /** + * 新しいアカウントへ移行します。 + *  ・フォロワーが新しいアカウントを自動でフォローします + *  ・このアカウントからのフォローは全て解除されます + *  ・このアカウントではノートの作成などができなくなります + * + * フォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。 + * リスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。 + * + * (この説明はこのサーバー(Misskey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。) + */ "moveAccountDescription": string; + /** + * アカウントの移行には、まずは移行先のアカウントでこのアカウントに対しエイリアスを作成します。 + * エイリアス作成後、移行先のアカウントを次のように入力してください: @username@server.example.com + */ "moveAccountHowTo": string; + /** + * 移行する + */ "startMigration": string; + /** + * 本当にこのアカウントを {account} に移行しますか?一度移行すると取り消せず、二度とこのアカウントを元の状態で使用できなくなります。 + */ "migrationConfirm": ParameterizedString<"account">; + /** + * + * アカウントは移行されています。 + * 移行を取り消すことはできません。 + */ "movedAndCannotBeUndone": string; + /** + * このアカウントからのフォロー解除は移行操作から24時間後に実行されます。 + * このアカウントのフォロー・フォロワー数は0になっています。フォロワーの解除はされないため、あなたのフォロワーはこのアカウントのフォロワー向け投稿を引き続き閲覧できます。 + */ "postMigrationNote": string; + /** + * 移行先のアカウント: + */ "movedTo": string; }; "_achievements": { + /** + * 獲得日時 + */ "earnedAt": string; "_types": { "_notes1": { + /** + * just setting up my msky + */ "title": string; + /** + * 初めてノートを投稿した + */ "description": string; + /** + * 良いMisskeyライフを! + */ "flavor": string; }; "_notes10": { + /** + * いくつかのノート + */ "title": string; + /** + * ノートを10回投稿した + */ "description": string; }; "_notes100": { + /** + * たくさんのノート + */ "title": string; + /** + * ノートを100回投稿した + */ "description": string; }; "_notes500": { + /** + * ノートまみれ + */ "title": string; + /** + * ノートを500回投稿した + */ "description": string; }; "_notes1000": { + /** + * ノートの山 + */ "title": string; + /** + * ノートを1,000回投稿した + */ "description": string; }; "_notes5000": { + /** + * 湧き出るノート + */ "title": string; + /** + * ノートを5,000回投稿した + */ "description": string; }; "_notes10000": { + /** + * スーパーノート + */ "title": string; + /** + * ノートを10,000回投稿した + */ "description": string; }; "_notes20000": { + /** + * ニードモアノート + */ "title": string; + /** + * ノートを20,000回投稿した + */ "description": string; }; "_notes30000": { + /** + * ノートノートノート + */ "title": string; + /** + * ノートを30,000回投稿した + */ "description": string; }; "_notes40000": { + /** + * ノート工場 + */ "title": string; + /** + * ノートを40,000回投稿した + */ "description": string; }; "_notes50000": { + /** + * ノートの惑星 + */ "title": string; + /** + * ノートを50,000回投稿した + */ "description": string; }; "_notes60000": { + /** + * ノートクエーサー + */ "title": string; + /** + * ノートを60,000回投稿した + */ "description": string; }; "_notes70000": { + /** + * ブラックノートホール + */ "title": string; + /** + * ノートを70,000回投稿した + */ "description": string; }; "_notes80000": { + /** + * ノートギャラクシー + */ "title": string; + /** + * ノートを80,000回投稿した + */ "description": string; }; "_notes90000": { + /** + * ノートバース + */ "title": string; + /** + * ノートを90,000回投稿した + */ "description": string; }; "_notes100000": { + /** + * ALL YOUR NOTE ARE BELONG TO US + */ "title": string; + /** + * ノートを100,000回投稿した + */ "description": string; + /** + * そんなに書くことある? + */ "flavor": string; }; "_login3": { + /** + * ビギナーⅠ + */ "title": string; + /** + * 通算ログイン日数が3日 + */ "description": string; + /** + * 今日からね僕は ミスキストってことで + */ "flavor": string; }; "_login7": { + /** + * ビギナーⅡ + */ "title": string; + /** + * 通算ログイン日数が7日 + */ "description": string; + /** + * 慣れてきましたか? + */ "flavor": string; }; "_login15": { + /** + * ビギナーⅢ + */ "title": string; + /** + * 通算ログイン日数が15日 + */ "description": string; }; "_login30": { + /** + * ミスキストⅠ + */ "title": string; + /** + * 通算ログイン日数が30日 + */ "description": string; }; "_login60": { + /** + * ミスキストⅡ + */ "title": string; + /** + * 通算ログイン日数が60日 + */ "description": string; }; "_login100": { + /** + * ミスキストⅢ + */ "title": string; + /** + * 通算ログイン日数が100日 + */ "description": string; + /** + * そのユーザー、ミスキストにつき + */ "flavor": string; }; "_login200": { + /** + * 常連Ⅰ + */ "title": string; + /** + * 通算ログイン日数が200日 + */ "description": string; }; "_login300": { + /** + * 常連Ⅱ + */ "title": string; + /** + * 通算ログイン日数が300日 + */ "description": string; }; "_login400": { + /** + * 常連Ⅲ + */ "title": string; + /** + * 通算ログイン日数が400日 + */ "description": string; }; "_login500": { + /** + * ベテランⅠ + */ "title": string; + /** + * 通算ログイン日数が500日 + */ "description": string; + /** + * 諸君、私はノートが好きだ + */ "flavor": string; }; "_login600": { + /** + * ベテランⅡ + */ "title": string; + /** + * 通算ログイン日数が600日 + */ "description": string; }; "_login700": { + /** + * ベテランⅢ + */ "title": string; + /** + * 通算ログイン日数が700日 + */ "description": string; }; "_login800": { + /** + * ノートマスターⅠ + */ "title": string; + /** + * 通算ログイン日数が800日 + */ "description": string; }; "_login900": { + /** + * ノートマスターⅡ + */ "title": string; + /** + * 通算ログイン日数が900日 + */ "description": string; }; "_login1000": { + /** + * ノートマスターⅢ + */ "title": string; + /** + * 通算ログイン日数が1,000日 + */ "description": string; + /** + * Misskeyを使ってくれてありがとう! + */ "flavor": string; }; "_noteClipped1": { + /** + * クリップせずにはいられないな + */ "title": string; + /** + * 初めてノートをクリップした + */ "description": string; }; "_noteFavorited1": { + /** + * 星をみるひと + */ "title": string; + /** + * 初めてノートをお気に入りに登録した + */ "description": string; }; "_myNoteFavorited1": { + /** + * 星が欲しい + */ "title": string; + /** + * 自分のノートが他の人からお気に入りに登録された + */ "description": string; }; "_profileFilled": { + /** + * 準備万端 + */ "title": string; + /** + * プロフィール設定を行った + */ "description": string; }; "_markedAsCat": { + /** + * 吾輩は猫である + */ "title": string; + /** + * アカウントをCatとして設定した + */ "description": string; + /** + * 名前はまだない。 + */ "flavor": string; }; "_following1": { + /** + * はじめてのフォロー + */ "title": string; + /** + * 初めてフォローした + */ "description": string; }; "_following10": { + /** + * ついてく、ついてく + */ "title": string; + /** + * フォローが10人を超した + */ "description": string; }; "_following50": { + /** + * 友達たくさん + */ "title": string; + /** + * フォローが50人を超した + */ "description": string; }; "_following100": { + /** + * 友達100人 + */ "title": string; + /** + * フォローが100人を超した + */ "description": string; }; "_following300": { + /** + * 友達過多 + */ "title": string; + /** + * フォローが300人を超した + */ "description": string; }; "_followers1": { + /** + * はじめてのフォロワー + */ "title": string; + /** + * 初めてフォローされた + */ "description": string; }; "_followers10": { + /** + * フォローミー! + */ "title": string; + /** + * フォロワーが10人を超した + */ "description": string; }; "_followers50": { + /** + * ぞろぞろ + */ "title": string; + /** + * フォロワーが50人を超した + */ "description": string; }; "_followers100": { + /** + * 人気者 + */ "title": string; + /** + * フォロワーが100人を超した + */ "description": string; }; "_followers300": { + /** + * 一列でお並びください + */ "title": string; + /** + * フォロワーが300人を超した + */ "description": string; }; "_followers500": { + /** + * 基地局 + */ "title": string; + /** + * フォロワーが500人を超した + */ "description": string; }; "_followers1000": { + /** + * インフルエンサー + */ "title": string; + /** + * フォロワーが1,000人を超した + */ "description": string; }; "_collectAchievements30": { + /** + * 実績コレクター + */ "title": string; + /** + * 実績を30個以上獲得した + */ "description": string; }; "_viewAchievements3min": { + /** + * 実績好き + */ "title": string; + /** + * 実績一覧を3分以上眺め続けた + */ "description": string; }; "_iLoveMisskey": { + /** + * I Love Misskey + */ "title": string; + /** + * "I ❤ #Misskey"を投稿した + */ "description": string; + /** + * Misskeyを使ってくださりありがとうございます! by 開発チーム + */ "flavor": string; }; "_foundTreasure": { + /** + * 宝探し + */ "title": string; + /** + * 隠されたお宝を発見した + */ "description": string; }; "_client30min": { + /** + * ひとやすみ + */ "title": string; + /** + * クライアントを起動してから30分以上経過した + */ "description": string; }; "_client60min": { + /** + * Misskeyの見すぎ + */ "title": string; + /** + * クライアントを起動してから60分以上経過した + */ "description": string; }; "_noteDeletedWithin1min": { + /** + * いまのなし + */ "title": string; + /** + * 投稿してから1分以内にその投稿を削除した + */ "description": string; }; "_postedAtLateNight": { + /** + * 夜行性 + */ "title": string; + /** + * 深夜にノートを投稿した + */ "description": string; + /** + * そろそろ寝よう。 + */ "flavor": string; }; "_postedAt0min0sec": { + /** + * 時報 + */ "title": string; + /** + * 0分0秒にノートを投稿した + */ "description": string; + /** + * ポッ ポッ ポッ ピーン + */ "flavor": string; }; "_selfQuote": { + /** + * 自己言及 + */ "title": string; + /** + * 自分のノートを引用した + */ "description": string; }; "_htl20npm": { + /** + * 流れるTL + */ "title": string; + /** + * ホームタイムラインの流速が20npmを越す + */ "description": string; }; "_viewInstanceChart": { + /** + * アナリスト + */ "title": string; + /** + * サーバーのチャートを表示した + */ "description": string; }; "_outputHelloWorldOnScratchpad": { + /** + * Hello, world! + */ "title": string; + /** + * スクラッチパッドで hello world を出力した + */ "description": string; }; "_open3windows": { + /** + * マルチウィンドウ + */ "title": string; + /** + * ウィンドウを3つ以上開いた状態にした + */ "description": string; }; "_driveFolderCircularReference": { + /** + * 循環参照 + */ "title": string; + /** + * ドライブのフォルダを再帰的な入れ子にしようとした + */ "description": string; }; "_reactWithoutRead": { + /** + * ちゃんと読んだ? + */ "title": string; + /** + * 100文字以上のテキストを含むノートに投稿されてから3秒以内にリアクションした + */ "description": string; }; "_clickedClickHere": { + /** + * ここをクリック + */ "title": string; + /** + * ここをクリックした + */ "description": string; }; "_justPlainLucky": { + /** + * 単なるラッキー + */ "title": string; + /** + * 10秒ごとに0.005%の確率で獲得 + */ "description": string; }; "_setNameToSyuilo": { + /** + * 神様コンプレックス + */ "title": string; + /** + * 名前を syuilo に設定した + */ "description": string; }; "_passedSinceAccountCreated1": { + /** + * 一周年 + */ "title": string; + /** + * アカウント作成から1年経過した + */ "description": string; }; "_passedSinceAccountCreated2": { + /** + * 二周年 + */ "title": string; + /** + * アカウント作成から2年経過した + */ "description": string; }; "_passedSinceAccountCreated3": { + /** + * 三周年 + */ "title": string; + /** + * アカウント作成から3年経過した + */ "description": string; }; "_loggedInOnBirthday": { + /** + * ハッピーバースデー + */ "title": string; + /** + * 誕生日にログインした + */ "description": string; }; "_loggedInOnNewYearsDay": { + /** + * あけましておめでとうございます + */ "title": string; + /** + * 元日にログインした + */ "description": string; + /** + * 今年も弊サーバーをよろしくお願いします + */ "flavor": string; }; "_cookieClicked": { + /** + * クッキーをクリックするゲーム + */ "title": string; + /** + * クッキーをクリックした + */ "description": string; + /** + * ソフト間違ってない? + */ "flavor": string; }; "_brainDiver": { + /** + * Brain Diver + */ "title": string; + /** + * Brain Diverへのリンクを投稿した + */ "description": string; + /** + * Misskey-Misskey La-Tu-Ma + */ "flavor": string; }; "_smashTestNotificationButton": { + /** + * テスト過剰 + */ "title": string; + /** + * 通知のテストをごく短時間のうちに連続して行った + */ "description": string; }; "_tutorialCompleted": { + /** + * Misskey初心者講座 修了証 + */ "title": string; + /** + * チュートリアルを完了した + */ "description": string; }; "_bubbleGameExplodingHead": { + /** + * 🤯 + */ "title": string; + /** + * バブルゲームで最も大きいモノを出した + */ "description": string; }; "_bubbleGameDoubleExplodingHead": { + /** + * ダブル🤯 + */ "title": string; + /** + * バブルゲームで最も大きいモノを2つ同時に出した + */ "description": string; + /** + * これくらいの おべんとばこに 🤯 🤯 ちょっとつめて + */ "flavor": string; }; }; }; "_role": { + /** + * ロールの作成 + */ "new": string; + /** + * ロールの編集 + */ "edit": string; + /** + * ロール名 + */ "name": string; + /** + * ロールの説明 + */ "description": string; + /** + * ロールの権限 + */ "permission": string; + /** + * モデレーターは基本的なモデレーションに関する操作を行えます。 + * 管理者はサーバーの全ての設定を変更できます。 + */ "descriptionOfPermission": string; + /** + * アサイン + */ "assignTarget": string; + /** + * マニュアルは誰がこのロールに含まれるかを手動で管理します。 + * コンディショナルは条件を設定し、それに合致するユーザーが自動で含まれるようになります。 + */ "descriptionOfAssignTarget": string; + /** + * マニュアル + */ "manual": string; + /** + * マニュアルロール + */ "manualRoles": string; + /** + * コンディショナル + */ "conditional": string; + /** + * コンディショナルロール + */ "conditionalRoles": string; + /** + * 条件 + */ "condition": string; + /** + * これはコンディショナルロールです。 + */ "isConditionalRole": string; + /** + * 公開ロール + */ "isPublic": string; + /** + * ユーザーのプロフィールでこのロールが表示されます。 + */ "descriptionOfIsPublic": string; + /** + * オプション + */ "options": string; + /** + * ポリシー + */ "policies": string; + /** + * ベースロール + */ "baseRole": string; + /** + * ベースロールの値を使用 + */ "useBaseValue": string; + /** + * アサインするロールを選択 + */ "chooseRoleToAssign": string; + /** + * アイコン画像のURL + */ "iconUrl": string; + /** + * バッジとして表示 + */ "asBadge": string; + /** + * オンにすると、ユーザー名の横にロールのアイコンが表示されます。 + */ "descriptionOfAsBadge": string; + /** + * ユーザーを見つけやすくする + */ "isExplorable": string; + /** + * オンにすると、「みつける」でメンバー一覧が公開されるほか、ロールのタイムラインが利用可能になります。 + */ "descriptionOfIsExplorable": string; + /** + * 表示順 + */ "displayOrder": string; + /** + * 数値が大きいほどUI上で先頭に表示されます。 + */ "descriptionOfDisplayOrder": string; + /** + * モデレーターのメンバー編集を許可 + */ "canEditMembersByModerator": string; + /** + * オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。 + */ "descriptionOfCanEditMembersByModerator": string; + /** + * 優先度 + */ "priority": string; "_priority": { + /** + * 低 + */ "low": string; + /** + * 中 + */ "middle": string; + /** + * 高 + */ "high": string; }; "_options": { + /** + * グローバルタイムラインの閲覧 + */ "gtlAvailable": string; + /** + * ローカルタイムラインの閲覧 + */ "ltlAvailable": string; + /** + * パブリック投稿の許可 + */ "canPublicNote": string; + /** + * サーバー招待コードの発行 + */ "canInvite": string; + /** + * 招待コードの作成可能数 + */ "inviteLimit": string; + /** + * 招待コードの発行間隔 + */ "inviteLimitCycle": string; + /** + * 招待コードの有効期限 + */ "inviteExpirationTime": string; + /** + * カスタム絵文字の管理 + */ "canManageCustomEmojis": string; + /** + * アバターデコレーションの管理 + */ "canManageAvatarDecorations": string; + /** + * ドライブ容量 + */ "driveCapacity": string; + /** + * ファイルにNSFWを常に付与 + */ "alwaysMarkNsfw": string; + /** + * ノートのピン留めの最大数 + */ "pinMax": string; + /** + * アンテナの作成可能数 + */ "antennaMax": string; + /** + * ワードミュートの最大文字数 + */ "wordMuteMax": string; + /** + * Webhookの作成可能数 + */ "webhookMax": string; + /** + * クリップの作成可能数 + */ "clipMax": string; + /** + * クリップ内のノートの最大数 + */ "noteEachClipsMax": string; + /** + * ユーザーリストの作成可能数 + */ "userListMax": string; + /** + * ユーザーリスト内のユーザーの最大数 + */ "userEachUserListsMax": string; + /** + * レートリミット + */ "rateLimitFactor": string; + /** + * 小さいほど制限が緩和され、大きいほど制限が強化されます。 + */ "descriptionOfRateLimitFactor": string; + /** + * 広告の非表示 + */ "canHideAds": string; + /** + * ノート検索の利用 + */ "canSearchNotes": string; + /** + * 翻訳機能の利用 + */ "canUseTranslator": string; + /** + * アイコンデコレーションの最大取付個数 + */ "avatarDecorationLimit": string; }; "_condition": { + /** + * ローカルユーザー + */ "isLocal": string; + /** + * リモートユーザー + */ "isRemote": string; + /** + * アカウント作成から~以内 + */ "createdLessThan": string; + /** + * アカウント作成から~経過 + */ "createdMoreThan": string; + /** + * フォロワー数が~以下 + */ "followersLessThanOrEq": string; + /** + * フォロワー数が~以上 + */ "followersMoreThanOrEq": string; + /** + * フォロー数が~以下 + */ "followingLessThanOrEq": string; + /** + * フォロー数が~以上 + */ "followingMoreThanOrEq": string; + /** + * 投稿数が~以下 + */ "notesLessThanOrEq": string; + /** + * 投稿数が~以上 + */ "notesMoreThanOrEq": string; + /** + * ~かつ~ + */ "and": string; + /** + * ~または~ + */ "or": string; + /** + * ~ではない + */ "not": string; }; }; "_sensitiveMediaDetection": { + /** + * 機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。 + */ "description": string; + /** + * 検出感度 + */ "sensitivity": string; + /** + * 感度を低くすると、誤検知(偽陽性)が減ります。感度を高くすると、検知漏れ(偽陰性)が減ります。 + */ "sensitivityDescription": string; + /** + * センシティブフラグを設定する + */ "setSensitiveFlagAutomatically": string; + /** + * この設定をオフにしても内部的に判定結果は保持されます。 + */ "setSensitiveFlagAutomaticallyDescription": string; + /** + * 動画の解析を有効化 + */ "analyzeVideos": string; + /** + * 静止画に加えて動画も解析するようにします。サーバーの負荷が少し増えます。 + */ "analyzeVideosDescription": string; }; "_emailUnavailable": { + /** + * 既に使用されています + */ "used": string; + /** + * 形式が正しくありません + */ "format": string; + /** + * 恒久的に使用可能なアドレスではありません + */ "disposable": string; + /** + * 正しいメールサーバーではありません + */ "mx": string; + /** + * メールサーバーが応答しません + */ "smtp": string; + /** + * このメールアドレスでは登録できません + */ "banned": string; }; "_ffVisibility": { + /** + * 公開 + */ "public": string; + /** + * フォロワーだけに公開 + */ "followers": string; + /** + * 非公開 + */ "private": string; }; "_signup": { + /** + * ほとんど完了です + */ "almostThere": string; + /** + * あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。 + */ "emailAddressInfo": string; + /** + * 入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。メールに記載されているリンクの有効期限は30分です。 + */ "emailSent": ParameterizedString<"email">; }; "_accountDelete": { + /** + * アカウントの削除 + */ "accountDelete": string; + /** + * アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。 + */ "mayTakeTime": string; + /** + * アカウントの削除が完了する際は、登録してあったメールアドレス宛に通知を送信します。 + */ "sendEmail": string; + /** + * アカウント削除をリクエスト + */ "requestAccountDelete": string; + /** + * 削除処理が開始されました。 + */ "started": string; + /** + * 削除が進行中 + */ "inProgress": string; }; "_ad": { + /** + * 戻る + */ "back": string; + /** + * この広告の表示頻度を下げる + */ "reduceFrequencyOfThisAd": string; + /** + * 表示しない + */ "hide": string; + /** + * 曜日はサーバーのタイムゾーンを元に指定されます。 + */ "timezoneinfo": string; + /** + * 広告配信設定 + */ "adsSettings": string; + /** + * リアルタイム更新中に広告を配信する間隔(ノートの個数) + */ "notesPerOneAd": string; + /** + * 0でリアルタイム更新時の広告配信を無効 + */ "setZeroToDisable": string; + /** + * 広告の配信間隔が極めて短いため、ユーザー体験が著しく損われる可能性があります。 + */ "adsTooClose": string; }; "_forgotPassword": { + /** + * アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。 + */ "enterEmail": string; + /** + * メールアドレスを登録していない場合は、管理者までお問い合わせください。 + */ "ifNoEmail": string; + /** + * このサーバーではメールがサポートされていないため、パスワードリセットを行う場合は管理者までお問い合わせください。 + */ "contactAdmin": string; }; "_gallery": { + /** + * 自分の投稿 + */ "my": string; + /** + * いいねした投稿 + */ "liked": string; + /** + * いいね! + */ "like": string; + /** + * いいね解除 + */ "unlike": string; }; "_email": { "_follow": { + /** + * フォローされました + */ "title": string; }; "_receiveFollowRequest": { + /** + * フォローリクエストを受け取りました + */ "title": string; }; }; "_plugin": { + /** + * プラグインのインストール + */ "install": string; + /** + * 信頼できないプラグインはインストールしないでください。 + */ "installWarn": string; + /** + * プラグインの管理 + */ "manage": string; + /** + * ソースを表示 + */ "viewSource": string; }; "_preferencesBackups": { + /** + * 作成したバックアップ + */ "list": string; + /** + * 新規保存 + */ "saveNew": string; + /** + * ファイルを読み込み + */ "loadFile": string; + /** + * このデバイスに適用 + */ "apply": string; + /** + * 上書き保存 + */ "save": string; + /** + * バックアップ名を入力 + */ "inputName": string; + /** + * 保存できません + */ "cannotSave": string; + /** + * バックアップ名「{name}」は既に存在します。違う名前を指定してください。 + */ "nameAlreadyExists": ParameterizedString<"name">; + /** + * バックアップ「{name}」を現在のデバイスに適用しますか?現在のデバイス設定は失われます。 + */ "applyConfirm": ParameterizedString<"name">; + /** + * {name}に上書き保存しますか? + */ "saveConfirm": ParameterizedString<"name">; + /** + * {name}を削除しますか? + */ "deleteConfirm": ParameterizedString<"name">; + /** + * 「{old}」を「{new}」に変更しますか? + */ "renameConfirm": ParameterizedString<"old" | "new">; + /** + * バックアップはありません。「新規保存」で現在のクライアント設定をサーバーに保存できます。 + */ "noBackups": string; + /** + * 作成日時: {date} {time} + */ "createdAt": ParameterizedString<"date" | "time">; + /** + * 更新日時: {date} {time} + */ "updatedAt": ParameterizedString<"date" | "time">; + /** + * 読み込みできません + */ "cannotLoad": string; + /** + * ファイル形式が違います。 + */ "invalidFile": string; }; "_registry": { + /** + * スコープ + */ "scope": string; + /** + * キー + */ "key": string; + /** + * キー + */ "keys": string; + /** + * ドメイン + */ "domain": string; + /** + * キーを作成 + */ "createKey": string; }; "_aboutMisskey": { + /** + * Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。 + */ "about": string; + /** + * コントリビューター + */ "contributors": string; + /** + * 全てのコントリビューター + */ "allContributors": string; + /** + * ソースコード + */ "source": string; + /** + * Misskeyを翻訳 + */ "translation": string; + /** + * Misskeyに寄付 + */ "donate": string; + /** + * 他にも多くの方が支援してくれています。ありがとうございます🥰 + */ "morePatrons": string; + /** + * 支援者 + */ "patrons": string; + /** + * プロジェクトメンバー + */ "projectMembers": string; }; "_displayOfSensitiveMedia": { + /** + * センシティブ設定されたメディアを隠す + */ "respect": string; + /** + * センシティブ設定されたメディアを隠さない + */ "ignore": string; + /** + * 常にメディアを隠す + */ "force": string; }; "_instanceTicker": { + /** + * 表示しない + */ "none": string; + /** + * リモートユーザーに表示 + */ "remote": string; + /** + * 常に表示 + */ "always": string; }; "_serverDisconnectedBehavior": { + /** + * 自動でリロード + */ "reload": string; + /** + * ダイアログで警告 + */ "dialog": string; + /** + * 控えめに警告 + */ "quiet": string; }; "_channel": { + /** + * チャンネルを作成 + */ "create": string; + /** + * チャンネルを編集 + */ "edit": string; + /** + * バナーを設定 + */ "setBanner": string; + /** + * バナーを削除 + */ "removeBanner": string; + /** + * トレンド + */ "featured": string; + /** + * 管理中 + */ "owned": string; + /** + * フォロー中 + */ "following": string; + /** + * {n}人が参加中 + */ "usersCount": ParameterizedString<"n">; + /** + * {n}投稿があります + */ "notesCount": ParameterizedString<"n">; + /** + * 名前と説明 + */ "nameAndDescription": string; + /** + * 名前のみ + */ "nameOnly": string; + /** + * チャンネル外へのリノートと引用リノートを許可する + */ "allowRenoteToExternal": string; }; "_menuDisplay": { + /** + * 横 + */ "sideFull": string; + /** + * 横(アイコン) + */ "sideIcon": string; + /** + * 上部 + */ "top": string; + /** + * 隠す + */ "hide": string; }; "_wordMute": { + /** + * ミュートするワード + */ "muteWords": string; + /** + * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。 + */ "muteWordsDescription": string; + /** + * キーワードをスラッシュで囲むと正規表現になります。 + */ "muteWordsDescription2": string; }; "_instanceMute": { + /** + * ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。 + */ "instanceMuteDescription": string; + /** + * 改行で区切って設定します + */ "instanceMuteDescription2": string; + /** + * 設定したサーバーのノートを隠します。 + */ "title": string; + /** + * ミュートするサーバー + */ "heading": string; }; "_theme": { + /** + * テーマを探す + */ "explore": string; + /** + * テーマのインストール + */ "install": string; + /** + * テーマの管理 + */ "manage": string; + /** + * テーマコード + */ "code": string; + /** + * 説明 + */ "description": string; + /** + * {name}をインストールしました + */ "installed": ParameterizedString<"name">; + /** + * インストールされたテーマ + */ "installedThemes": string; + /** + * 標準のテーマ + */ "builtinThemes": string; + /** + * そのテーマは既にインストールされています + */ "alreadyInstalled": string; + /** + * テーマの形式が間違っています + */ "invalid": string; + /** + * テーマを作る + */ "make": string; + /** + * ベース + */ "base": string; + /** + * 定数を追加 + */ "addConstant": string; + /** + * 定数 + */ "constant": string; + /** + * デフォルト値 + */ "defaultValue": string; + /** + * 色 + */ "color": string; + /** + * プロパティを参照 + */ "refProp": string; + /** + * 定数を参照 + */ "refConst": string; + /** + * キー + */ "key": string; + /** + * 関数 + */ "func": string; + /** + * 関数の種類 + */ "funcKind": string; + /** + * 引数 + */ "argument": string; + /** + * 元にするプロパティの名前 + */ "basedProp": string; + /** + * 不透明度 + */ "alpha": string; + /** + * 暗さ + */ "darken": string; + /** + * 明るさ + */ "lighten": string; + /** + * 定数名を入力してください + */ "inputConstantName": string; + /** + * ここにテーマコードを貼り付けて、エディターにインポートできます + */ "importInfo": string; + /** + * 定数 {const} を削除しても良いですか? + */ "deleteConstantConfirm": ParameterizedString<"const">; "keys": { + /** + * アクセント + */ "accent": string; + /** + * 背景 + */ "bg": string; + /** + * 文字 + */ "fg": string; + /** + * フォーカス + */ "focus": string; + /** + * インジケーター + */ "indicator": string; + /** + * パネル + */ "panel": string; + /** + * 影 + */ "shadow": string; + /** + * ヘッダー + */ "header": string; + /** + * サイドバーの背景 + */ "navBg": string; + /** + * サイドバーの文字 + */ "navFg": string; + /** + * サイドバー文字(ホバー) + */ "navHoverFg": string; + /** + * サイドバー文字(アクティブ) + */ "navActive": string; + /** + * サイドバーのインジケーター + */ "navIndicator": string; + /** + * リンク + */ "link": string; + /** + * ハッシュタグ + */ "hashtag": string; + /** + * メンション + */ "mention": string; + /** + * あなた宛てメンション + */ "mentionMe": string; + /** + * Renote + */ "renote": string; + /** + * モーダルの背景 + */ "modalBg": string; + /** + * 分割線 + */ "divider": string; + /** + * スクロールバーの取っ手 + */ "scrollbarHandle": string; + /** + * スクロールバーの取っ手(ホバー) + */ "scrollbarHandleHover": string; + /** + * 日付ラベルの文字 + */ "dateLabelFg": string; + /** + * 情報の背景 + */ "infoBg": string; + /** + * 情報の文字 + */ "infoFg": string; + /** + * 警告の背景 + */ "infoWarnBg": string; + /** + * 警告の文字 + */ "infoWarnFg": string; + /** + * 通知トーストの背景 + */ "toastBg": string; + /** + * 通知トーストの文字 + */ "toastFg": string; + /** + * ボタンの背景 + */ "buttonBg": string; + /** + * ボタンの背景 (ホバー) + */ "buttonHoverBg": string; + /** + * 入力ボックスの縁取り + */ "inputBorder": string; + /** + * リスト項目の背景 (ホバー) + */ "listItemHoverBg": string; + /** + * ドライブフォルダーの背景 + */ "driveFolderBg": string; + /** + * 壁紙のオーバーレイ + */ "wallpaperOverlay": string; + /** + * バッジ + */ "badge": string; + /** + * チャットの背景 + */ "messageBg": string; + /** + * アクセント (暗め) + */ "accentDarken": string; + /** + * アクセント (明るめ) + */ "accentLighten": string; + /** + * 強調された文字 + */ "fgHighlighted": string; }; }; "_sfx": { + /** + * ノート + */ "note": string; + /** + * ノート(自分) + */ "noteMy": string; + /** + * 通知 + */ "notification": string; + /** + * アンテナ受信 + */ "antenna": string; + /** + * チャンネル通知 + */ "channel": string; + /** + * リアクション選択時 + */ "reaction": string; }; "_soundSettings": { + /** + * ドライブの音声を使用 + */ "driveFile": string; + /** + * ドライブのファイルを選択してください + */ "driveFileWarn": string; + /** + * このファイルは対応していません + */ "driveFileTypeWarn": string; + /** + * 音声ファイルを選択してください + */ "driveFileTypeWarnDescription": string; + /** + * 音声が長すぎます + */ "driveFileDurationWarn": string; + /** + * 長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか? + */ "driveFileDurationWarnDescription": string; }; "_ago": { + /** + * 未来 + */ "future": string; + /** + * たった今 + */ "justNow": string; + /** + * {n}秒前 + */ "secondsAgo": ParameterizedString<"n">; + /** + * {n}分前 + */ "minutesAgo": ParameterizedString<"n">; + /** + * {n}時間前 + */ "hoursAgo": ParameterizedString<"n">; + /** + * {n}日前 + */ "daysAgo": ParameterizedString<"n">; + /** + * {n}週間前 + */ "weeksAgo": ParameterizedString<"n">; + /** + * {n}ヶ月前 + */ "monthsAgo": ParameterizedString<"n">; + /** + * {n}年前 + */ "yearsAgo": ParameterizedString<"n">; + /** + * 日時の解析に失敗 + */ "invalid": string; }; "_timeIn": { + /** + * {n}秒後 + */ "seconds": ParameterizedString<"n">; + /** + * {n}分後 + */ "minutes": ParameterizedString<"n">; + /** + * {n}時間後 + */ "hours": ParameterizedString<"n">; + /** + * {n}日後 + */ "days": ParameterizedString<"n">; + /** + * {n}週間後 + */ "weeks": ParameterizedString<"n">; + /** + * {n}ヶ月後 + */ "months": ParameterizedString<"n">; + /** + * {n}年後 + */ "years": ParameterizedString<"n">; }; "_time": { + /** + * 秒 + */ "second": string; + /** + * 分 + */ "minute": string; + /** + * 時間 + */ "hour": string; + /** + * 日 + */ "day": string; }; "_2fa": { + /** + * 既に設定は完了しています。 + */ "alreadyRegistered": string; + /** + * 認証アプリの設定を開始 + */ "registerTOTP": string; + /** + * まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。 + */ "step1": ParameterizedString<"a" | "b">; + /** + * 次に、表示されているQRコードをアプリでスキャンします。 + */ "step2": string; + /** + * QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。 + */ "step2Click": string; + /** + * デスクトップアプリを使用する場合は次のURIを入力します + */ "step2Uri": string; + /** + * 確認コードを入力 + */ "step3Title": string; + /** + * アプリに表示されている確認コード(トークン)を入力します。 + */ "step3": string; + /** + * 設定が完了しました + */ "setupCompleted": string; + /** + * これからログインするときも、同じようにコードを入力します。 + */ "step4": string; + /** + * お使いのブラウザはセキュリティキーに対応していません。 + */ "securityKeyNotSupported": string; + /** + * セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。 + */ "registerTOTPBeforeKey": string; + /** + * FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。 + */ "securityKeyInfo": string; + /** + * セキュリティキー・パスキーを登録する + */ "registerSecurityKey": string; + /** + * キーの名前を入力 + */ "securityKeyName": string; + /** + * ブラウザの指示に従い、セキュリティキーやパスキーを登録してください + */ "tapSecurityKey": string; + /** + * セキュリティキーを削除 + */ "removeKey": string; + /** + * {name}を削除しますか? + */ "removeKeyConfirm": ParameterizedString<"name">; + /** + * セキュリティキーが登録されている場合、認証アプリの設定は解除できません。 + */ "whyTOTPOnlyRenew": string; + /** + * 認証アプリを再設定 + */ "renewTOTP": string; + /** + * 今までの認証アプリの確認コードおよびバックアップコードは使用できなくなります + */ "renewTOTPConfirm": string; + /** + * 再設定する + */ "renewTOTPOk": string; + /** + * やめておく + */ "renewTOTPCancel": string; + /** + * このウィザードを閉じる前に、以下のバックアップコードを確認してください。 + */ "checkBackupCodesBeforeCloseThisWizard": string; + /** + * バックアップコード + */ "backupCodes": string; + /** + * 認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。 + */ "backupCodesDescription": string; + /** + * バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。 + */ "backupCodeUsedWarning": string; + /** + * バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。 + */ "backupCodesExhaustedWarning": string; }; "_permissions": { + /** + * アカウントの情報を見る + */ "read:account": string; + /** + * アカウントの情報を変更する + */ "write:account": string; + /** + * ブロックを見る + */ "read:blocks": string; + /** + * ブロックを操作する + */ "write:blocks": string; + /** + * ドライブを見る + */ "read:drive": string; + /** + * ドライブを操作する + */ "write:drive": string; + /** + * お気に入りを見る + */ "read:favorites": string; + /** + * お気に入りを操作する + */ "write:favorites": string; + /** + * フォローの情報を見る + */ "read:following": string; + /** + * フォロー・フォロー解除する + */ "write:following": string; + /** + * チャットを見る + */ "read:messaging": string; + /** + * チャットを操作する + */ "write:messaging": string; + /** + * ミュートを見る + */ "read:mutes": string; + /** + * ミュートを操作する + */ "write:mutes": string; + /** + * ノートを作成・削除する + */ "write:notes": string; + /** + * 通知を見る + */ "read:notifications": string; + /** + * 通知を操作する + */ "write:notifications": string; + /** + * リアクションを見る + */ "read:reactions": string; + /** + * リアクションを操作する + */ "write:reactions": string; + /** + * 投票する + */ "write:votes": string; + /** + * ページを見る + */ "read:pages": string; + /** + * ページを操作する + */ "write:pages": string; + /** + * ページのいいねを見る + */ "read:page-likes": string; + /** + * ページのいいねを操作する + */ "write:page-likes": string; + /** + * ユーザーグループを見る + */ "read:user-groups": string; + /** + * ユーザーグループを操作する + */ "write:user-groups": string; + /** + * チャンネルを見る + */ "read:channels": string; + /** + * チャンネルを操作する + */ "write:channels": string; + /** + * ギャラリーを見る + */ "read:gallery": string; + /** + * ギャラリーを操作する + */ "write:gallery": string; + /** + * ギャラリーのいいねを見る + */ "read:gallery-likes": string; + /** + * ギャラリーのいいねを操作する + */ "write:gallery-likes": string; + /** + * Playを見る + */ "read:flash": string; + /** + * Playを操作する + */ "write:flash": string; + /** + * Playのいいねを見る + */ "read:flash-likes": string; + /** + * Playのいいねを操作する + */ "write:flash-likes": string; + /** + * ユーザーからの通報を見る + */ "read:admin:abuse-user-reports": string; + /** + * ユーザーアカウントを削除する + */ "write:admin:delete-account": string; + /** + * ユーザーのすべてのファイルを削除する + */ "write:admin:delete-all-files-of-a-user": string; + /** + * データベースインデックスに関する情報を見る + */ "read:admin:index-stats": string; + /** + * データベーステーブルに関する情報を見る + */ "read:admin:table-stats": string; + /** + * ユーザーのIPアドレスを見る + */ "read:admin:user-ips": string; + /** + * インスタンスのメタデータを見る + */ "read:admin:meta": string; + /** + * ユーザーのパスワードをリセットする + */ "write:admin:reset-password": string; + /** + * ユーザーからの通報を解決する + */ "write:admin:resolve-abuse-user-report": string; + /** + * メールを送る + */ "write:admin:send-email": string; + /** + * サーバーの情報を見る + */ "read:admin:server-info": string; + /** + * モデレーションログを見る + */ "read:admin:show-moderation-log": string; + /** + * ユーザーのプライベートな情報を見る + */ "read:admin:show-user": string; + /** + * ユーザーのプライベートな情報を見る + */ "read:admin:show-users": string; + /** + * ユーザーを凍結する + */ "write:admin:suspend-user": string; + /** + * ユーザーのアバターを削除する + */ "write:admin:unset-user-avatar": string; + /** + * ユーザーのバーナーを削除する + */ "write:admin:unset-user-banner": string; + /** + * ユーザーの凍結を解除する + */ "write:admin:unsuspend-user": string; + /** + * インスタンスのメタデータを操作する + */ "write:admin:meta": string; + /** + * モデレーションノートを操作する + */ "write:admin:user-note": string; + /** + * ロールを操作する + */ "write:admin:roles": string; + /** + * ロールを見る + */ "read:admin:roles": string; + /** + * リレーを操作する + */ "write:admin:relays": string; + /** + * リレーを見る + */ "read:admin:relays": string; + /** + * 招待コードを操作する + */ "write:admin:invite-codes": string; + /** + * 招待コードを見る + */ "read:admin:invite-codes": string; + /** + * お知らせを操作する + */ "write:admin:announcements": string; + /** + * お知らせを見る + */ "read:admin:announcements": string; + /** + * アバターデコレーションを操作する + */ "write:admin:avatar-decorations": string; + /** + * アバターデコレーションを見る + */ "read:admin:avatar-decorations": string; + /** + * 連合に関する情報を操作する + */ "write:admin:federation": string; + /** + * ユーザーアカウントを操作する + */ "write:admin:account": string; + /** + * ユーザーに関する情報を見る + */ "read:admin:account": string; + /** + * 絵文字を操作する + */ "write:admin:emoji": string; + /** + * 絵文字を見る + */ "read:admin:emoji": string; + /** + * ジョブキューを操作する + */ "write:admin:queue": string; + /** + * ジョブキューに関する情報を見る + */ "read:admin:queue": string; + /** + * プロモーションノートを操作する + */ "write:admin:promo": string; + /** + * ユーザーのドライブを操作する + */ "write:admin:drive": string; + /** + * ユーザーのドライブの関する情報を見る + */ "read:admin:drive": string; + /** + * 管理者用のWebsocket APIを使う + */ "read:admin:stream": string; + /** + * 広告を操作する + */ "write:admin:ad": string; + /** + * 広告を見る + */ "read:admin:ad": string; + /** + * 招待コードを作成する + */ "write:invite-codes": string; + /** + * 招待コードを取得する + */ "read:invite-codes": string; + /** + * クリップのいいねを操作する + */ "write:clip-favorite": string; + /** + * クリップのいいねを見る + */ "read:clip-favorite": string; + /** + * 連合に関する情報を取得する + */ "read:federation": string; + /** + * 違反を報告する + */ "write:report-abuse": string; }; "_auth": { + /** + * アプリへのアクセス許可 + */ "shareAccessTitle": string; + /** + * 「{name}」がアカウントにアクセスすることを許可しますか? + */ "shareAccess": ParameterizedString<"name">; + /** + * アカウントへのアクセスを許可しますか? + */ "shareAccessAsk": string; + /** + * {name}は次の権限を要求しています + */ "permission": ParameterizedString<"name">; + /** + * このアプリは次の権限を要求しています + */ "permissionAsk": string; + /** + * アプリケーションに戻ってやっていってください + */ "pleaseGoBack": string; + /** + * アプリケーションに戻っています + */ "callback": string; + /** + * アクセスを拒否しました + */ "denied": string; + /** + * アプリケーションにアクセス許可を与えるには、ログインが必要です。 + */ "pleaseLogin": string; }; "_antennaSources": { + /** + * 全てのノート + */ "all": string; + /** + * フォローしているユーザーのノート + */ "homeTimeline": string; + /** + * 指定した一人または複数のユーザーのノート + */ "users": string; + /** + * 指定したリストのユーザーのノート + */ "userList": string; + /** + * 指定した一人または複数のユーザーを除いた全てのノート + */ "userBlacklist": string; }; "_weekday": { + /** + * 日曜日 + */ "sunday": string; + /** + * 月曜日 + */ "monday": string; + /** + * 火曜日 + */ "tuesday": string; + /** + * 水曜日 + */ "wednesday": string; + /** + * 木曜日 + */ "thursday": string; + /** + * 金曜日 + */ "friday": string; + /** + * 土曜日 + */ "saturday": string; }; "_widgets": { + /** + * プロフィール + */ "profile": string; + /** + * サーバー情報 + */ "instanceInfo": string; + /** + * 付箋 + */ "memo": string; + /** + * 通知 + */ "notifications": string; + /** + * タイムライン + */ "timeline": string; + /** + * カレンダー + */ "calendar": string; + /** + * トレンド + */ "trends": string; + /** + * 時計 + */ "clock": string; + /** + * RSSリーダー + */ "rss": string; + /** + * RSSティッカー + */ "rssTicker": string; + /** + * アクティビティ + */ "activity": string; + /** + * フォト + */ "photos": string; + /** + * デジタル時計 + */ "digitalClock": string; + /** + * UNIX時計 + */ "unixClock": string; + /** + * 連合 + */ "federation": string; + /** + * サーバークラウド + */ "instanceCloud": string; + /** + * 投稿フォーム + */ "postForm": string; + /** + * スライドショー + */ "slideshow": string; + /** + * ボタン + */ "button": string; + /** + * オンラインユーザー + */ "onlineUsers": string; + /** + * ジョブキュー + */ "jobQueue": string; + /** + * サーバーメトリクス + */ "serverMetric": string; + /** + * AiScriptコンソール + */ "aiscript": string; + /** + * AiScript App + */ "aiscriptApp": string; + /** + * 藍 + */ "aichan": string; + /** + * ユーザーリスト + */ "userList": string; "_userList": { + /** + * リストを選択 + */ "chooseList": string; }; + /** + * クリッカー + */ "clicker": string; + /** + * 今日誕生日のユーザー + */ "birthdayFollowings": string; }; "_cw": { + /** + * 隠す + */ "hide": string; + /** + * もっと見る + */ "show": string; + /** + * {count}文字 + */ "chars": ParameterizedString<"count">; + /** + * {count}ファイル + */ "files": ParameterizedString<"count">; }; "_poll": { + /** + * 選択肢は最低2つ必要です + */ "noOnlyOneChoice": string; + /** + * 選択肢{n} + */ "choiceN": ParameterizedString<"n">; + /** + * これ以上追加できません + */ "noMore": string; + /** + * 複数回答可 + */ "canMultipleVote": string; + /** + * 期限 + */ "expiration": string; + /** + * 無期限 + */ "infinite": string; + /** + * 日時指定 + */ "at": string; + /** + * 経過指定 + */ "after": string; + /** + * 期日 + */ "deadlineDate": string; + /** + * 時間 + */ "deadlineTime": string; + /** + * 期間 + */ "duration": string; + /** + * {n}票 + */ "votesCount": ParameterizedString<"n">; + /** + * 計{n}票 + */ "totalVotes": ParameterizedString<"n">; + /** + * 投票する + */ "vote": string; + /** + * 結果を見る + */ "showResult": string; + /** + * 投票済み + */ "voted": string; + /** + * 終了済み + */ "closed": string; + /** + * 終了まであと{d}日{h}時間 + */ "remainingDays": ParameterizedString<"d" | "h">; + /** + * 終了まであと{h}時間{m}分 + */ "remainingHours": ParameterizedString<"h" | "m">; + /** + * 終了まであと{m}分{s}秒 + */ "remainingMinutes": ParameterizedString<"m" | "s">; + /** + * 終了まであと{s}秒 + */ "remainingSeconds": ParameterizedString<"s">; }; "_visibility": { + /** + * パブリック + */ "public": string; + /** + * 全てのユーザーに公開 + */ "publicDescription": string; + /** + * ホーム + */ "home": string; + /** + * ホームタイムラインのみに公開 + */ "homeDescription": string; + /** + * フォロワー + */ "followers": string; + /** + * 自分のフォロワーのみに公開 + */ "followersDescription": string; + /** + * ダイレクト + */ "specified": string; + /** + * 指定したユーザーのみに公開 + */ "specifiedDescription": string; + /** + * 連合なし + */ "disableFederation": string; + /** + * 他サーバーへの配信を行いません + */ "disableFederationDescription": string; }; "_postForm": { + /** + * このノートに返信... + */ "replyPlaceholder": string; + /** + * このノートを引用... + */ "quotePlaceholder": string; + /** + * チャンネルに投稿... + */ "channelPlaceholder": string; "_placeholders": { + /** + * いまどうしてる? + */ "a": string; + /** + * 何かありましたか? + */ "b": string; + /** + * 何をお考えですか? + */ "c": string; + /** + * 言いたいことは? + */ "d": string; + /** + * ここに書いてください + */ "e": string; + /** + * あなたが書くのを待っています... + */ "f": string; }; }; "_profile": { + /** + * 名前 + */ "name": string; + /** + * ユーザー名 + */ "username": string; + /** + * 自己紹介 + */ "description": string; + /** + * ハッシュタグを含めることができます。 + */ "youCanIncludeHashtags": string; + /** + * 追加情報 + */ "metadata": string; + /** + * 追加情報を編集 + */ "metadataEdit": string; + /** + * プロフィールに表として追加情報を表示することができます。 + */ "metadataDescription": string; + /** + * ラベル + */ "metadataLabel": string; + /** + * 内容 + */ "metadataContent": string; + /** + * アイコン画像を変更 + */ "changeAvatar": string; + /** + * バナー画像を変更 + */ "changeBanner": string; + /** + * 内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。 + */ "verifiedLinkDescription": string; + /** + * 最大{max}つまでデコレーションを付けられます。 + */ "avatarDecorationMax": ParameterizedString<"max">; }; "_exportOrImport": { + /** + * 全てのノート + */ "allNotes": string; + /** + * お気に入りにしたノート + */ "favoritedNotes": string; + /** + * クリップ + */ "clips": string; + /** + * フォロー + */ "followingList": string; + /** + * ミュート + */ "muteList": string; + /** + * ブロック + */ "blockingList": string; + /** + * リスト + */ "userLists": string; + /** + * ミュートしているユーザーを除外 + */ "excludeMutingUsers": string; + /** + * 使われていないアカウントを除外 + */ "excludeInactiveUsers": string; + /** + * インポートした人による返信をTLに含むようにする + */ "withReplies": string; }; "_charts": { + /** + * 連合 + */ "federation": string; + /** + * リクエスト + */ "apRequest": string; + /** + * ユーザーの増減 + */ "usersIncDec": string; + /** + * ユーザーの合計 + */ "usersTotal": string; + /** + * アクティブユーザー数 + */ "activeUsers": string; + /** + * ノートの増減 + */ "notesIncDec": string; + /** + * ローカルのノートの増減 + */ "localNotesIncDec": string; + /** + * リモートのノートの増減 + */ "remoteNotesIncDec": string; + /** + * ノートの合計 + */ "notesTotal": string; + /** + * ファイルの増減 + */ "filesIncDec": string; + /** + * ファイルの合計 + */ "filesTotal": string; + /** + * ストレージ使用量の増減 + */ "storageUsageIncDec": string; + /** + * ストレージ使用量の合計 + */ "storageUsageTotal": string; }; "_instanceCharts": { + /** + * リクエスト + */ "requests": string; + /** + * ユーザーの増減 + */ "users": string; + /** + * ユーザーの累積 + */ "usersTotal": string; + /** + * ノートの増減 + */ "notes": string; + /** + * ノートの累積 + */ "notesTotal": string; + /** + * フォロー/フォロワーの増減 + */ "ff": string; + /** + * フォロー/フォロワーの累積 + */ "ffTotal": string; + /** + * キャッシュサイズの増減 + */ "cacheSize": string; + /** + * キャッシュサイズの累積 + */ "cacheSizeTotal": string; + /** + * ファイル数の増減 + */ "files": string; + /** + * ファイル数の累積 + */ "filesTotal": string; }; "_timelines": { + /** + * ホーム + */ "home": string; + /** + * ローカル + */ "local": string; + /** + * ソーシャル + */ "social": string; + /** + * グローバル + */ "global": string; }; "_play": { + /** + * Playの作成 + */ "new": string; + /** + * Playの編集 + */ "edit": string; + /** + * Playを作成しました + */ "created": string; + /** + * Playを更新しました + */ "updated": string; + /** + * Playを削除しました + */ "deleted": string; + /** + * Play設定 + */ "pageSetting": string; + /** + * このPlayを編集 + */ "editThisPage": string; + /** + * ソースを表示 + */ "viewSource": string; + /** + * 自分のPlay + */ "my": string; + /** + * いいねしたPlay + */ "liked": string; + /** + * 人気 + */ "featured": string; + /** + * タイトル + */ "title": string; + /** + * スクリプト + */ "script": string; + /** + * 説明 + */ "summary": string; }; "_pages": { + /** + * ページの作成 + */ "newPage": string; + /** + * ページの編集 + */ "editPage": string; + /** + * ソースを表示中 + */ "readPage": string; + /** + * ページを作成しました + */ "created": string; + /** + * ページを更新しました + */ "updated": string; + /** + * ページを削除しました + */ "deleted": string; + /** + * ページ設定 + */ "pageSetting": string; + /** + * 指定されたページURLは既に存在しています + */ "nameAlreadyExists": string; + /** + * 不正なページURLです + */ "invalidNameTitle": string; + /** + * 空白でないか確認してください + */ "invalidNameText": string; + /** + * このページを編集 + */ "editThisPage": string; + /** + * ソースを表示 + */ "viewSource": string; + /** + * ページを見る + */ "viewPage": string; + /** + * いいね + */ "like": string; + /** + * いいね解除 + */ "unlike": string; + /** + * 自分のページ + */ "my": string; + /** + * いいねしたページ + */ "liked": string; + /** + * 人気 + */ "featured": string; + /** + * インスペクター + */ "inspector": string; + /** + * コンテンツ + */ "contents": string; + /** + * ページブロック + */ "content": string; + /** + * 変数 + */ "variables": string; + /** + * タイトル + */ "title": string; + /** + * ページURL + */ "url": string; + /** + * ページの要約 + */ "summary": string; + /** + * 中央寄せ + */ "alignCenter": string; + /** + * ピン留めされているときにタイトルを非表示 + */ "hideTitleWhenPinned": string; + /** + * フォント + */ "font": string; + /** + * セリフ + */ "fontSerif": string; + /** + * サンセリフ + */ "fontSansSerif": string; + /** + * アイキャッチ画像を設定 + */ "eyeCatchingImageSet": string; + /** + * アイキャッチ画像を削除 + */ "eyeCatchingImageRemove": string; + /** + * ブロックを追加 + */ "chooseBlock": string; + /** + * 種類を選択 + */ "selectType": string; + /** + * コンテンツ + */ "contentBlocks": string; + /** + * 入力 + */ "inputBlocks": string; + /** + * 特殊 + */ "specialBlocks": string; "blocks": { + /** + * テキスト + */ "text": string; + /** + * テキストエリア + */ "textarea": string; + /** + * セクション + */ "section": string; + /** + * 画像 + */ "image": string; + /** + * ボタン + */ "button": string; + /** + * ノート埋め込み + */ "note": string; "_note": { + /** + * ノートID + */ "id": string; + /** + * ノートURLをペーストして設定することもできます。 + */ "idDescription": string; + /** + * 詳細な表示 + */ "detailed": string; }; }; }; "_relayStatus": { + /** + * 承認待ち + */ "requesting": string; + /** + * 承認済み + */ "accepted": string; + /** + * 拒否済み + */ "rejected": string; }; "_notification": { + /** + * ファイルがアップロードされました + */ "fileUploaded": string; + /** + * {name}からのメンション + */ "youGotMention": ParameterizedString<"name">; + /** + * {name}からのリプライ + */ "youGotReply": ParameterizedString<"name">; + /** + * {name}による引用 + */ "youGotQuote": ParameterizedString<"name">; + /** + * {name}がRenoteしました + */ "youRenoted": ParameterizedString<"name">; + /** + * フォローされました + */ "youWereFollowed": string; + /** + * フォローリクエストが来ました + */ "youReceivedFollowRequest": string; + /** + * フォローリクエストが承認されました + */ "yourFollowRequestAccepted": string; + /** + * アンケートの結果が出ました + */ "pollEnded": string; + /** + * 新しい投稿 + */ "newNote": string; + /** + * アンテナ {name} + */ "unreadAntennaNote": ParameterizedString<"name">; + /** + * ロールが付与されました + */ "roleAssigned": string; + /** + * プッシュ通知の更新をしました + */ "emptyPushNotificationMessage": string; + /** + * 実績を獲得 + */ "achievementEarned": string; + /** + * 通知テスト + */ "testNotification": string; + /** + * 通知の表示を確かめる + */ "checkNotificationBehavior": string; + /** + * テスト通知を送信する + */ "sendTestNotification": string; + /** + * 通知はこのように表示されます + */ "notificationWillBeDisplayedLikeThis": string; + /** + * {n}人がリアクションしました + */ "reactedBySomeUsers": ParameterizedString<"n">; + /** + * {n}人がリノートしました + */ "renotedBySomeUsers": ParameterizedString<"n">; + /** + * {n}人にフォローされました + */ "followedBySomeUsers": ParameterizedString<"n">; "_types": { + /** + * すべて + */ "all": string; + /** + * ユーザーの新規投稿 + */ "note": string; + /** + * フォロー + */ "follow": string; + /** + * メンション + */ "mention": string; + /** + * リプライ + */ "reply": string; + /** + * Renote + */ "renote": string; + /** + * 引用 + */ "quote": string; + /** + * リアクション + */ "reaction": string; + /** + * アンケートが終了 + */ "pollEnded": string; + /** + * フォロー申請を受け取った + */ "receiveFollowRequest": string; + /** + * フォローが受理された + */ "followRequestAccepted": string; + /** + * ロールが付与された + */ "roleAssigned": string; + /** + * 実績の獲得 + */ "achievementEarned": string; + /** + * 連携アプリからの通知 + */ "app": string; }; "_actions": { + /** + * フォローバック + */ "followBack": string; + /** + * 返信 + */ "reply": string; + /** + * Renote + */ "renote": string; }; }; "_deck": { + /** + * 常にメインカラムを表示 + */ "alwaysShowMainColumn": string; + /** + * カラムの寄せ + */ "columnAlign": string; + /** + * カラムを追加 + */ "addColumn": string; + /** + * カラムの設定 + */ "configureColumn": string; + /** + * 左に移動 + */ "swapLeft": string; + /** + * 右に移動 + */ "swapRight": string; + /** + * 上に移動 + */ "swapUp": string; + /** + * 下に移動 + */ "swapDown": string; + /** + * 左にスタック + */ "stackLeft": string; + /** + * 右に出す + */ "popRight": string; + /** + * プロファイル + */ "profile": string; + /** + * 新規プロファイル + */ "newProfile": string; + /** + * プロファイルを削除 + */ "deleteProfile": string; + /** + * カラムを組み合わせて自分だけのインターフェイスを作りましょう! + */ "introduction": string; + /** + * 画面の右にある + を押して、いつでもカラムを追加できます。 + */ "introduction2": string; + /** + * カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください + */ "widgetsIntroduction": string; + /** + * 非ルートページは簡易UIで表示 + */ "useSimpleUiForNonRootPages": string; + /** + * 「幅を自動調整」が有効の場合、これが幅の最小値となります + */ "usedAsMinWidthWhenFlexible": string; + /** + * 幅を自動調整 + */ "flexible": string; "_columns": { + /** + * メイン + */ "main": string; + /** + * ウィジェット + */ "widgets": string; + /** + * 通知 + */ "notifications": string; + /** + * タイムライン + */ "tl": string; + /** + * アンテナ + */ "antenna": string; + /** + * リスト + */ "list": string; + /** + * チャンネル + */ "channel": string; + /** + * あなた宛て + */ "mentions": string; + /** + * ダイレクト + */ "direct": string; + /** + * ロールタイムライン + */ "roleTimeline": string; }; }; "_dialog": { + /** + * 最大文字数を超えています! 現在 {current} / 制限 {max} + */ "charactersExceeded": ParameterizedString<"current" | "max">; + /** + * 最小文字数を下回っています! 現在 {current} / 制限 {min} + */ "charactersBelow": ParameterizedString<"current" | "min">; }; "_disabledTimeline": { + /** + * 無効化されたタイムライン + */ "title": string; + /** + * 現在のロールでは、このタイムラインを使用することはできません。 + */ "description": string; }; "_drivecleaner": { + /** + * サイズが大きい順 + */ "orderBySizeDesc": string; + /** + * 追加日が古い順 + */ "orderByCreatedAtAsc": string; }; "_webhookSettings": { + /** + * Webhookを作成 + */ "createWebhook": string; + /** + * 名前 + */ "name": string; + /** + * シークレット + */ "secret": string; + /** + * Webhookを実行するタイミング + */ "events": string; + /** + * 有効 + */ "active": string; "_events": { + /** + * フォローしたとき + */ "follow": string; + /** + * フォローされたとき + */ "followed": string; + /** + * ノートを投稿したとき + */ "note": string; + /** + * 返信されたとき + */ "reply": string; + /** + * Renoteされたとき + */ "renote": string; + /** + * リアクションがあったとき + */ "reaction": string; + /** + * メンションされたとき + */ "mention": string; }; }; "_moderationLogTypes": { + /** + * ロールを作成 + */ "createRole": string; + /** + * ロールを削除 + */ "deleteRole": string; + /** + * ロールを更新 + */ "updateRole": string; + /** + * ロールへアサイン + */ "assignRole": string; + /** + * ロールのアサイン解除 + */ "unassignRole": string; + /** + * 凍結 + */ "suspend": string; + /** + * 凍結解除 + */ "unsuspend": string; + /** + * カスタム絵文字追加 + */ "addCustomEmoji": string; + /** + * カスタム絵文字更新 + */ "updateCustomEmoji": string; + /** + * カスタム絵文字削除 + */ "deleteCustomEmoji": string; + /** + * サーバー設定更新 + */ "updateServerSettings": string; + /** + * モデレーションノート更新 + */ "updateUserNote": string; + /** + * ファイルを削除 + */ "deleteDriveFile": string; + /** + * ノートを削除 + */ "deleteNote": string; + /** + * 全体のお知らせを作成 + */ "createGlobalAnnouncement": string; + /** + * ユーザーへお知らせを作成 + */ "createUserAnnouncement": string; + /** + * 全体のお知らせを更新 + */ "updateGlobalAnnouncement": string; + /** + * ユーザーのお知らせを更新 + */ "updateUserAnnouncement": string; + /** + * 全体のお知らせを削除 + */ "deleteGlobalAnnouncement": string; + /** + * ユーザーのお知らせを削除 + */ "deleteUserAnnouncement": string; + /** + * パスワードをリセット + */ "resetPassword": string; + /** + * リモートサーバーを停止 + */ "suspendRemoteInstance": string; + /** + * リモートサーバーを再開 + */ "unsuspendRemoteInstance": string; + /** + * ファイルをセンシティブ付与 + */ "markSensitiveDriveFile": string; + /** + * ファイルをセンシティブ解除 + */ "unmarkSensitiveDriveFile": string; + /** + * 通報を解決 + */ "resolveAbuseReport": string; + /** + * 招待コードを作成 + */ "createInvitation": string; + /** + * 広告を作成 + */ "createAd": string; + /** + * 広告を削除 + */ "deleteAd": string; + /** + * 広告を更新 + */ "updateAd": string; + /** + * アイコンデコレーションを作成 + */ "createAvatarDecoration": string; + /** + * アイコンデコレーションを更新 + */ "updateAvatarDecoration": string; + /** + * アイコンデコレーションを削除 + */ "deleteAvatarDecoration": string; + /** + * ユーザーのアイコンを解除 + */ "unsetUserAvatar": string; + /** + * ユーザーのバナーを解除 + */ "unsetUserBanner": string; }; "_fileViewer": { + /** + * ファイルの詳細 + */ "title": string; + /** + * ファイルタイプ + */ "type": string; + /** + * ファイルサイズ + */ "size": string; + /** + * URL + */ "url": string; + /** + * 追加日 + */ "uploadedAt": string; + /** + * 添付されているノート + */ "attachedNotes": string; + /** + * このページは、このファイルをアップロードしたユーザーしか閲覧できません。 + */ "thisPageCanBeSeenFromTheAuthor": string; }; "_externalResourceInstaller": { + /** + * 外部サイトからインストール + */ "title": string; + /** + * 配布元が信頼できるかを確認した上でインストールしてください。 + */ "checkVendorBeforeInstall": string; "_plugin": { + /** + * このプラグインをインストールしますか? + */ "title": string; + /** + * プラグイン情報 + */ "metaTitle": string; }; "_theme": { + /** + * このテーマをインストールしますか? + */ "title": string; + /** + * テーマ情報 + */ "metaTitle": string; }; "_meta": { + /** + * 基本のカラースキーム + */ "base": string; }; "_vendorInfo": { + /** + * 配布元情報 + */ "title": string; + /** + * 参照したエンドポイント + */ "endpoint": string; + /** + * ファイル整合性の確認 + */ "hashVerify": string; }; "_errors": { "_invalidParams": { + /** + * パラメータが不足しています + */ "title": string; + /** + * 外部サイトからデータを取得するために必要な情報が不足しています。URLをお確かめください。 + */ "description": string; }; "_resourceTypeNotSupported": { + /** + * この外部リソースには対応していません + */ "title": string; + /** + * この外部サイトから取得したリソースの種別には対応していません。サイト管理者にお問い合わせください。 + */ "description": string; }; "_failedToFetch": { + /** + * データの取得に失敗しました + */ "title": string; + /** + * 外部サイトとの通信に失敗しました。もう一度試しても改善しない場合、サイト管理者にお問い合わせください。 + */ "fetchErrorDescription": string; + /** + * 外部サイトから取得したデータが読み取れませんでした。サイト管理者にお問い合わせください。 + */ "parseErrorDescription": string; }; "_hashUnmatched": { + /** + * 正しいデータが取得できませんでした + */ "title": string; + /** + * 提供されたデータの整合性の確認に失敗しました。セキュリティ上、インストールは続行できません。サイト管理者にお問い合わせください。 + */ "description": string; }; "_pluginParseFailed": { + /** + * AiScript エラー + */ "title": string; + /** + * データは取得できたものの、AiScriptの解析時にエラーがあったため読み込めませんでした。プラグインの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。 + */ "description": string; }; "_pluginInstallFailed": { + /** + * プラグインのインストールに失敗しました + */ "title": string; + /** + * プラグインのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。 + */ "description": string; }; "_themeParseFailed": { + /** + * テーマ解析エラー + */ "title": string; + /** + * データは取得できたものの、テーマファイルの解析時にエラーがあったため読み込めませんでした。テーマの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。 + */ "description": string; }; "_themeInstallFailed": { + /** + * テーマのインストールに失敗しました + */ "title": string; + /** + * テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。 + */ "description": string; }; }; }; "_dataSaver": { "_media": { + /** + * メディアの読み込み + */ "title": string; + /** + * 画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。 + */ "description": string; }; "_avatar": { + /** + * アイコン画像 + */ "title": string; + /** + * アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。 + */ "description": string; }; "_urlPreview": { + /** + * URLプレビューのサムネイル + */ "title": string; + /** + * URLプレビューのサムネイル画像が読み込まれなくなります。 + */ "description": string; }; "_code": { + /** + * コードハイライト + */ "title": string; + /** + * MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。 + */ "description": string; }; }; "_reversi": { + /** + * リバーシ + */ "reversi": string; + /** + * 対局の設定 + */ "gameSettings": string; + /** + * ボードを選択 + */ "chooseBoard": string; + /** + * 先行/後攻 + */ "blackOrWhite": string; + /** + * {name}が黒(先行) + */ "blackIs": ParameterizedString<"name">; + /** + * ルール + */ "rules": string; + /** + * 対局はまもなく開始されます + */ "thisGameIsStartedSoon": string; + /** + * 相手の準備が完了するのを待っています + */ "waitingForOther": string; + /** + * あなたの準備が完了するのを待っています + */ "waitingForMe": string; + /** + * 準備してください + */ "waitingBoth": string; + /** + * 準備完了 + */ "ready": string; + /** + * 準備を再開 + */ "cancelReady": string; + /** + * 相手のターンです + */ "opponentTurn": string; + /** + * あなたのターンです + */ "myTurn": string; + /** + * {name}のターンです + */ "turnOf": ParameterizedString<"name">; + /** + * {name}のターン + */ "pastTurnOf": ParameterizedString<"name">; + /** + * 投了 + */ "surrender": string; + /** + * 投了により + */ "surrendered": string; + /** + * 引き分け + */ "drawn": string; + /** + * {name}の勝ち + */ "won": ParameterizedString<"name">; + /** + * 黒 + */ "black": string; + /** + * 白 + */ "white": string; + /** + * 合計 + */ "total": string; + /** + * {count}ターン目 + */ "turnCount": ParameterizedString<"count">; + /** + * 自分の対局 + */ "myGames": string; + /** + * みんなの対局 + */ "allGames": string; + /** + * 終了 + */ "ended": string; + /** + * 対局中 + */ "playing": string; + /** + * 石の少ない方が勝ち(ロセオ) + */ "isLlotheo": string; + /** + * ループマップ + */ "loopedMap": string; + /** + * どこでも置けるモード + */ "canPutEverywhere": string; + /** + * フリーマッチ + */ "freeMatch": string; + /** + * 対戦相手を探しています + */ "lookingForPlayer": string; }; } 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 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 {{ i18n.ts.loadMore }}
-
{{ i18n.t('empty-draghover') }}
-
{{ i18n.ts.emptyDrive }}
{{ i18n.t('empty-drive-description') }}
+
{{ i18n.ts['empty-draghover'] }}
+
{{ i18n.ts.emptyDrive }}
{{ i18n.ts['empty-drive-description'] }}
{{ i18n.ts.emptyFolder }}
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
- {{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: + {{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}:
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
- {{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: + {{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}:
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 {{ i18n.ts._notification.achievementEarned }} {{ i18n.ts._notification.testNotification }} - {{ i18n.t('_notification.reactedBySomeUsers', { n: notification.reactions.length }) }} - {{ i18n.t('_notification.renotedBySomeUsers', { n: notification.users.length }) }} + {{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }} + {{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }} {{ notification.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 {{ i18n.ts.disableAll }} {{ i18n.ts.enableAll }}
- {{ i18n.t(`_notification._types.${ntype}`) }} + {{ i18n.ts._notification._types[ntype] }}
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 - ({{ i18n.t('_poll.votesCount', { n: choice.votes }) }}) + ({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})

- {{ i18n.t('_poll.totalVotes', { n: total }) }} + {{ i18n.tsx._poll.totalVotes({ n: total }) }} · {{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }} {{ i18n.ts._poll.voted }} @@ -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

  • - +
- ({{ i18n.t('withNFiles', { n: note.files.length }) }}) + ({{ i18n.tsx.withNFiles({ n: note.files.length }) }})
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 {{ i18n.ts.enableAll }}
- {{ i18n.t(`_permissions.${kind}`) }} + {{ i18n.ts._permissions[kind] }}
{{ i18n.ts.adminPermission }}
- {{ i18n.t(`_permissions.${kind}`) }} + {{ i18n.ts._permissions[kind] }}
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
{{ i18n.ts.help }} -
{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}
+
{{ i18n.tsx._initialAccountSetting.haveFun({ name: instance.name ?? host }) }}
{{ i18n.ts.goBack }} {{ i18n.ts.close }} 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
{{ i18n.ts.pushNotification }}
-
{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}
+
{{ i18n.tsx._initialAccountSetting.pushNotificationDescription({ name: instance.name ?? host }) }}
{{ i18n.ts.goBack }} @@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}
-
{{ i18n.t('_initialAccountSetting.youCanContinueTutorial', { name: instance.name ?? host }) }}
+
{{ i18n.tsx._initialAccountSetting.youCanContinueTutorial({ name: instance.name ?? host }) }}
{{ i18n.ts._initialAccountSetting.startTutorial }}
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
- + {{ i18n.ts.add }} {{ i18n.ts.close }} @@ -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 @@ + + + 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(() => { 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 -
+
{{ i18n.ts.aboutMisskey }}
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
-
{{ i18n.t('recentNHours', { n: 90 }) }}
+
{{ i18n.tsx.recentNHours({ n: 90 }) }}
-
{{ i18n.t('recentNDays', { n: 90 }) }}
+
{{ i18n.tsx.recentNDays({ n: 90 }) }}
@@ -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 {{ i18n.ts._announcement.needConfirmationToRead }} -

{{ i18n.t('nUsersRead', { n: announcement.reads }) }}

+

{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}

{{ i18n.ts.save }} {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }}) @@ -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 @@ -30,10 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only 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 - {{ i18n.t(`_relayStatus.${relay.status}`) }} + {{ i18n.ts._relayStatus[relay.status] }}
{{ i18n.ts.remove }}
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