diff --git a/.config/example.yml b/.config/example.yml index fcf22debc..b996a83fb 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -82,6 +82,8 @@ redis: #pass: example-pass #prefix: example-prefix #db: 1 + # You can specify more ioredis options... + #username: example-username #redisForPubsub: # host: localhost @@ -90,6 +92,8 @@ redis: # #pass: example-pass # #prefix: example-prefix # #db: 1 +# # You can specify more ioredis options... +# #username: example-username #redisForJobQueue: # host: localhost @@ -98,6 +102,8 @@ redis: # #pass: example-pass # #prefix: example-prefix # #db: 1 +# # You can specify more ioredis options... +# #username: example-username # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/CHANGELOG.md b/CHANGELOG.md index c14e07c98..438436f01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,17 +15,19 @@ ## 13.x.x (unreleased) ### General -- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました -- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました - 招待機能を改善しました * 過去に発行した招待コードを確認できるようになりました * ロールごとに招待コードの発行数制限と制限対象期間、有効期限を設定できるようになりました * 招待コードを作成したユーザーと使用したユーザーを確認できるようになりました +- ユーザーにロールが期限付きでアサインされている場合、その期限をユーザーのモデレーションページで確認できるようになりました +- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました +- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました ### Client - deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように - ドライブファイルのメニューで画像をクロップできるように - 画像を動画と同様に簡単に隠せるように +- Enhance: ノートの埋め込みが複数画像と動画を表示されるように - オリジナル画像を保持せずにアップロードする場合webpでアップロードされるように(Safari以外) - 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように - フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように @@ -41,7 +43,9 @@ - ロール設定画面でロールIDを確認できるように - コンテキストメニュー表示時のパフォーマンスを改善 - フォロー/フォロワー非公開時の表示を改善 -- AiScriptを0.14.0に更新 +- 本文にMFMが含まれている場合に自動でたたまれる機能が、返信先や引用RNにも適用されるように + - position は対象外になりました +- AiScriptを0.15.0に更新 - Fix: サーバーメトリクスが90度傾いている - Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正 - Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 @@ -58,15 +62,19 @@ - nsfwjs のモデルロードを排他することで、重複ロードによってメモリ使用量が増加しないように - 連合の配送ジョブのパフォーマンスを向上(ロック機構の見直し、Redisキャッシュの活用) - featuredノートのsignedGet回数を減らしました -- リモートサーバーからのNSFW映像のキャッシュだけを無効化できるオプションを追加 +- ActivityPubの署名用鍵長を2048bitに変更しパフォーマンスを向上(新規アカウントのみ) +- リモートサーバーのセンシティブなファイルのキャッシュだけを無効化できるオプションを追加 - MeilisearchにIndexするノートの範囲を設定できるように - Export notes with file detail - Add unix socket support +- 設定ファイルでioredisの全てのオプションを指定可能に +- Fix: エクスポートしたカスタム絵文字のzipが大きいと読み込めない問題を修正 - Fix: リモートサーバーに無意味なActivityPubの配信を行うことがあるのを修正 - Fix: Remove Meilisearch index when notes are deleted - Fix: 非英語環境でのPostgreSQLのエラーハンドリングを修正 - Fix: インスタンスのアイコンがbase64の場合の挙動を修正 - Fix: ローカルの `Person` を指す `acct` URI を解析するときのバグを修正しました +- Fix: 無効化されたアンテナが再度有効化されないことがある問題を修正 ## 13.13.2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 896fb6b08..62bc11cd9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -214,30 +214,13 @@ Misskey uses [Storybook](https://storybook.js.org/) for UI development. ### Setup & Run -#### Universal - -##### Setup - -```bash -pnpm --filter misskey-js build -pnpm --filter frontend tsc -p .storybook && (node packages/frontend/.storybook/preload-locale.js & node packages/frontend/.storybook/preload-theme.js) -``` - -##### Run - -```bash -node packages/frontend/.storybook/generate.js && pnpm --filter frontend storybook dev -``` - -#### macOS & Linux - -##### Setup +#### Setup ```bash pnpm --filter misskey-js build ``` -##### Run +#### Run ```bash pnpm --filter frontend storybook-dev diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js index 2bf91cb00..5ab07c748 100644 --- a/cypress/e2e/basic.cy.js +++ b/cypress/e2e/basic.cy.js @@ -54,6 +54,7 @@ describe('After setup instance', () => { cy.get('[data-cy-signup]').click(); cy.get('[data-cy-signup-rules-continue]').should('be.disabled'); cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click(); + cy.get('[data-cy-modal-dialog-ok]').click(); cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled'); cy.get('[data-cy-signup-rules-continue]').click(); @@ -78,6 +79,7 @@ describe('After setup instance', () => { cy.get('[data-cy-signup]').click(); cy.get('[data-cy-signup-rules-continue]').should('be.disabled'); cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click(); + cy.get('[data-cy-modal-dialog-ok]').click(); cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled'); cy.get('[data-cy-signup-rules-continue]').click(); diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 16a4b538f..040e8836e 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1091,6 +1091,9 @@ usedAt: "Benutzt am" unused: "Unbenutzt" used: "Benutzt" expired: "Abgelaufen" +doYouAgree: "Zustimmen?" +beSureToReadThisAsItIsImportant: "Lies bitte diese wichtige Informationen." +iHaveReadXCarefullyAndAgree: "Ich habe den Text \"{x}\" gelesen und stimme zu." _initialAccountSetting: accountCreated: "Dein Konto wurde erfolgreich erstellt!" letsStartAccountSetup: "Lass uns nun dein Konto einrichten." diff --git a/locales/en-US.yml b/locales/en-US.yml index 503ea5294..ea8252551 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1091,6 +1091,9 @@ usedAt: "Used at" unused: "Unused" used: "Used" expired: "Expired" +doYouAgree: "Agree?" +beSureToReadThisAsItIsImportant: "Please read this important information." +iHaveReadXCarefullyAndAgree: "I have read the text \"{x}\" and agree." _initialAccountSetting: accountCreated: "Your account was successfully created!" letsStartAccountSetup: "For starters, let's set up your profile." diff --git a/locales/index.d.ts b/locales/index.d.ts index 8eba7fe14..400fc7e58 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1094,6 +1094,9 @@ export interface Locale { "unused": string; "used": string; "expired": string; + "doYouAgree": string; + "beSureToReadThisAsItIsImportant": string; + "iHaveReadXCarefullyAndAgree": string; "_initialAccountSetting": { "accountCreated": string; "letsStartAccountSetup": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d16e99a55..e6b980a4d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1042,7 +1042,7 @@ vertical: "縦" horizontal: "横" position: "位置" serverRules: "サーバールール" -pleaseConfirmBelowBeforeSignup: "このサーバーに登録する前に、以下を確認してください。" +pleaseConfirmBelowBeforeSignup: "このサーバーに登録するには、以下の内容を確認し同意する必要があります。" pleaseAgreeAllToContinue: "続けるには、全ての「同意する」にチェックが入っている必要があります。" continue: "続ける" preservedUsernames: "予約ユーザー名" @@ -1091,6 +1091,9 @@ usedAt: "使用日時" unused: "未使用" used: "使用済み" expired: "期限切れ" +doYouAgree: "同意しますか?" +beSureToReadThisAsItIsImportant: "重要ですので必ずお読みください。" +iHaveReadXCarefullyAndAgree: "「{x}」の内容をよく読み、同意します。" _initialAccountSetting: accountCreated: "アカウントの作成が完了しました!" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 3f4ec957a..e8b02f8d5 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1067,6 +1067,9 @@ branding: "あ" enableServerMachineStats: "サーバーのマシン情報見せびらかすで" enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする" turnOffToImprovePerformance: "オフにしたらえらい軽うなるで。" +inviteCodeCreated: "招待コード作ったで" +inviteLimitExceeded: "招待コード作りすぎやで。" +createLimitRemaining: "作成できる招待コード: 残り {limit} 個やで" unused: "つこてへん" used: "もうつこてる" _initialAccountSetting: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 09e2d942d..42a829c12 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -40,7 +40,7 @@ favorites: "즐겨찾기" unfavorite: "즐겨찾기에서 제거" favorited: "즐겨찾기에 등록했습니다" alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다" -cantFavorite: "즐겨찾기에 등록하지 못했습니다." +cantFavorite: "즐겨찾기에 등록하지 못했습니다" pin: "프로필에 고정" unpin: "프로필에서 고정 해제" copyContent: "내용 복사" @@ -108,7 +108,7 @@ renote: "리노트" unrenote: "리노트 취소" renoted: "리노트했습니다" cantRenote: "이 게시물은 리노트 할 수 없습니다." -cantReRenote: "리노트를 리노트 할 수 없습니다." +cantReRenote: "리노트를 리노트할 수 없습니다." quote: "인용" inChannelRenote: "채널 내 리노트" inChannelQuote: "채널 내 인용" @@ -116,7 +116,7 @@ pinnedNote: "고정해놓은 노트" pinned: "프로필에 고정" you: "당신" clickToShow: "클릭하여 보기" -sensitive: "열람주의" +sensitive: "열람 주의" add: "추가" reaction: "리액션" reactions: "리액션" @@ -161,7 +161,7 @@ cacheRemoteSensitiveFilesDescription: "이 설정을 비활성화하면 리모 flagAsBot: "나는 봇입니다" flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다." flagAsCat: "나는 고양이다냥" -flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요." +flagAsCatDescription: "이 계정이 고양이라면 활성화해 주세요." flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기" flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다." autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락" @@ -207,7 +207,7 @@ instanceInfo: "서버 정보" statistics: "통계" clearQueue: "대기열 비우기" clearQueueConfirmTitle: "대기열을 비우시겠습니까?" -clearQueueConfirmText: "대기열에 남아 있는 노트는 더이상 연합되지 않습니다. 보통의 경우 이 작업은 필요하지 않습니다." +clearQueueConfirmText: "대기열에 남아 있는 노트는 더 이상 연합되지 않습니다. 보통의 경우 이 작업은 필요하지 않습니다." clearCachedFiles: "캐시 비우기" clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?" blockedInstances: "차단된 서버" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index e102a0579..37775dfb1 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -152,7 +152,7 @@ emoji: "表情符號" emojis: "表情符號" emojiName: "表情符號名稱" emojiUrl: "表情符號URL" -addEmoji: "加入表情符號" +addEmoji: "新增表情符號" settingGuide: "推薦設定" cacheRemoteFiles: "快取遠端檔案" cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外連接數據。" @@ -160,12 +160,12 @@ cacheRemoteSensitiveFiles: "快取遠端的敏感檔案" cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。" flagAsBot: "此使用者是機器人" flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Misskey內部系統將本帳戶識別為機器人。" -flagAsCat: "喵~~~~~~~~~~~~~~!!!!!!!!!!!!" +flagAsCat: "此帳戶是一隻貓,喵~~~!!!" flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示" flagShowTimelineReplies: "在時間軸上顯示貼文的回覆" flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。" autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求" -addAccount: "添加帳戶" +addAccount: "新增帳戶" reloadAccountsList: "更新帳戶清單的資訊" loginFailed: "登入失敗" showOnRemote: "轉到所在實例顯示" @@ -260,7 +260,7 @@ saved: "已儲存" messaging: "聊天" upload: "上傳" keepOriginalUploading: "保留原圖" -keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成一張用於web發布的圖片。" +keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成適用於網路傳送的版本。" fromDrive: "從雲端空間" fromUrl: "從URL" uploadFromUrl: "從網址上傳" @@ -279,7 +279,7 @@ basicNotesBeforeCreateAccount: "基本注意事項" termsOfService: "服務條款" start: "開始" home: "首頁" -remoteUserCaution: "由於該使用者來自遠端實例,因此資訊可能非即時的。" +remoteUserCaution: "由於該使用者來自其他實例,因此其資訊可能不完整。" activity: "動態" images: "圖片" image: "圖片" @@ -289,12 +289,12 @@ registeredDate: "註冊日期" location: "位置" theme: "外觀主題" themeForLightMode: "在淺色模式下使用的主題" -themeForDarkMode: "在黑暗模式下使用的主題" +themeForDarkMode: "在深色模式下使用的主題" light: "淺色" -dark: "黑暗" +dark: "深色" lightThemes: "明亮主題" darkThemes: "黑暗主題" -syncDeviceDarkMode: "將黑暗模式與設備設置同步" +syncDeviceDarkMode: "同步至此裝置的深色模式設定" drive: "雲端硬碟" fileName: "檔案名稱" selectFile: "選擇檔案" @@ -319,7 +319,7 @@ copyUrl: "複製URL" rename: "重新命名" avatar: "大頭貼" banner: "橫幅" -displayOfSensitiveMedia: "敏感性媒體的顯示" +displayOfSensitiveMedia: "顯示敏感媒體" whenServerDisconnected: "與伺服器的連接中斷時" disconnectedFromServer: "與伺服器中斷連線" reload: "重新整理" @@ -343,18 +343,18 @@ monthX: "{month}月" yearX: "{year}年" pages: "頁面" integration: "整合" -connectService: "己連結" -disconnectService: "己斷開 " -enableLocalTimeline: "開啟本地時間軸" +connectService: "已連結" +disconnectService: "已斷開 " +enableLocalTimeline: "啟用本地時間軸" enableGlobalTimeline: "啟用全域時間軸" disablingTimelinesInfo: "為了方便,即使您關閉了時間線功能,管理員和審查員仍可以繼續使用。" registration: "註冊" -enableRegistration: "開啟新使用者註冊" +enableRegistration: "開放新使用者註冊" invite: "邀請" driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小" driveCapacityPerRemoteAccount: "每個非本地用戶的雲端空間大小" inMb: "以Mbps為單位" -iconUrl: "圖標URL (例如 favicon)" +iconUrl: "圖標 URL(例如 favicon)" bannerUrl: "橫幅圖片URL" backgroundImageUrl: "背景圖片的來源網址 " basicInfo: "基本資訊" @@ -376,26 +376,26 @@ turnstile: "Turnstile" enableTurnstile: "啟用 Turnstile" turnstileSiteKey: "網站金鑰" turnstileSecretKey: "金鑰" -avoidMultiCaptchaConfirm: "使用多種驗證方式可能會造成干擾,您要關閉其他驗證方式嗎?您可以按“取消”保留多種驗證方式。" +avoidMultiCaptchaConfirm: "使用多種驗證方式可能會造成干擾,您要關閉其他驗證方式嗎?您可以按「取消」保留多種驗證方式。" antennas: "天線" manageAntennas: "管理天線" name: "名稱" antennaSource: "接收來源" antennaKeywords: "包含關鍵字" antennaExcludeKeywords: "排除關鍵字" -antennaKeywordsDescription: "用空格分隔指定AND、用換行符分隔指定OR" +antennaKeywordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)" notifyAntenna: "通知有新貼文" withFileAntenna: "僅帶有附件的貼文" -enableServiceworker: "開啟 ServiceWorker" -antennaUsersDescription: "指定用換行符分隔的用戶名" +enableServiceworker: "啟用 ServiceWorker" +antennaUsersDescription: "填寫使用者名稱,以換行分隔" caseSensitive: "區分大小寫" withReplies: "包含回覆" connectedTo: "您的帳戶已連接到以下社交帳戶" notesAndReplies: "貼文與回覆" withFiles: "附件" silence: "禁言" -silenceConfirm: "確定要靜音此使用者嗎?" -unsilence: "解除靜音" +silenceConfirm: "確定要禁言此帳戶嗎?" +unsilence: "解除禁言" unsilenceConfirm: "確定要解除禁言嗎?" popularUsers: "熱門使用者" recentlyUpdatedUsers: "最近發文的使用者" @@ -409,25 +409,25 @@ about: "關於" aboutMisskey: "關於 Misskey" administrator: "管理員" token: "權杖" -2fa: "雙因素驗證" +2fa: "雙重驗證" totp: "驗證應用程式" totpDescription: "以驗證應用程式輸入一次性密碼" moderator: "審查員" moderation: "審查" nUsersMentioned: "被提及到 {n} 次" -securityKeyAndPasskey: "安全金鑰・Passkey" +securityKeyAndPasskey: "安全金鑰、Passkey" securityKey: "安全金鑰" lastUsed: "上次使用" -lastUsedAt: "最後使用:{t}" +lastUsedAt: "上次使用:{t}" unregister: "註銷帳戶" passwordLessLogin: "設置無密碼登入" passwordLessLoginDescription: "不使用密碼,以安全金鑰或 Passkey 登入" -resetPassword: "重置密碼" +resetPassword: "重設密碼" newPasswordIs: "新密碼為「{password}」" reduceUiAnimation: "減少介面的動態視覺" share: "分享" notFound: "找不到" -notFoundDescription: "找不到與指定URL回應的頁面" +notFoundDescription: "找不到該 URL 的頁面" uploadFolder: "預設上傳資料夾" cacheClear: "清除快取" markAsReadAllNotifications: "標記所有通知為已讀" @@ -476,17 +476,17 @@ disableDrawer: "不顯示下拉式選單" showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項" noHistory: "沒有歷史紀錄" signinHistory: "登入歷史" -enableAdvancedMfm: "啟用高級MFM" -enableAnimatedMfm: "啟用MFM動畫" +enableAdvancedMfm: "啟用高級 MFM" +enableAnimatedMfm: "啟用 MFM 動畫" doing: "正在進行" category: "類別" tags: "標籤" docSource: "文件來源" createAccount: "建立帳戶" existingAccount: "現有帳戶" -regenerate: "再生" +regenerate: "再次生成" fontSize: "字體大小" -mediaListWithOneImageAppearance: "僅1枚圖片的媒體列表高度" +mediaListWithOneImageAppearance: "只有一張圖片時的媒體列表高度" limitTo: "上限為{x}" noFollowRequests: "沒有追隨您的請求" openImageInNewTab: "於新分頁中開啟圖片" @@ -495,7 +495,7 @@ local: "本地" remote: "遠端" total: "合計" weekOverWeekChanges: "與上週相比" -dayOverDayChanges: "與前一日相比" +dayOverDayChanges: "與昨日相比" appearance: "外觀" clientSettings: "客戶端設定" accountSettings: "帳戶設定" @@ -507,26 +507,26 @@ showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦" objectStorage: "Object Storage (物件儲存)" useObjectStorage: "使用Object Storage" objectStorageBaseUrl: "Base URL" -objectStorageBaseUrlDesc: "引用時的URL。如果您使用的是CDN或反向代理,請指定其URL,例如S3:'https://.s3.amazonaws.com'、GCS:'https://storage.googleapis.com/'。" +objectStorageBaseUrlDesc: "引用時的 URL。如果您使用的是 CDN 或反向代理,請指定其 URL,例如 S3(https://.s3.amazonaws.com)、GCS(https://storage.googleapis.com/)。" objectStorageBucket: "儲存空間(Bucket)" -objectStorageBucketDesc: "請指定您正在使用的服務的存儲桶名稱。 " +objectStorageBucketDesc: "請填寫所用服務的儲存空間(Bucket)名稱。 " objectStoragePrefix: "前綴" -objectStoragePrefixDesc: "它存儲在此前綴目錄下。" +objectStoragePrefixDesc: "它儲存在此前綴目錄下。" objectStorageEndpoint: "端點(Endpoint)" -objectStorageEndpointDesc: "如要使用AWS S3,請留空。否則請依照你使用的服務商的說明書進行設定,以''或 ':'的形式設定端點(Endpoint)。" +objectStorageEndpointDesc: "如使用 AWS S3,請留空。如使用其他服務,請按照其說明文件以「」或「:」的形式設定端點(Endpoint)。" objectStorageRegion: "地域(Region)" -objectStorageRegionDesc: "指定一個分區,例如“xx-east-1”。 如果您使用的服務沒有分區的概念,請留空或填寫“us-east-1”。" -objectStorageUseSSL: "使用SSL" -objectStorageUseSSLDesc: "如果不使用https進行API連接,請關閉" +objectStorageRegionDesc: "請填寫一個分區,例如「xx-east-1」。 如果您使用的服務不設分區,請留空或填寫「us-east-1」。" +objectStorageUseSSL: "使用 SSL" +objectStorageUseSSLDesc: "請在不使用 https 連接 API 時關閉" objectStorageUseProxy: "使用網路代理" -objectStorageUseProxyDesc: "如果不使用代理進行API連接,請關閉" -objectStorageSetPublicRead: "上傳時設定為\"public-read\"" -s3ForcePathStyleDesc: "啟用 s3ForcePathStyle 會強制將儲存槽名稱指定為 URL 中路徑的一部分,而不是主機名。 使用自託管 Minio 之類的可能需要啟用。" +objectStorageUseProxyDesc: "請在不使用網路代理連接 API 時關閉" +objectStorageSetPublicRead: "上傳時設定為「public-read」" +s3ForcePathStyleDesc: "啟用 s3ForcePathStyle 將強制填寫儲存空間(Bucket)名稱至 URL 路徑內,而非寫入主機名。 使用如 Minio 等自行託管服務時可能需要啟用。" serverLogs: "伺服器日誌" deleteAll: "刪除所有記錄" showFixedPostForm: "於時間軸頁頂顯示「發送貼文」方框" showFixedPostFormInChannel: "於時間軸頁頂顯示「發送貼文」方框(頻道)" -newNoteRecived: "發現新的貼文" +newNoteRecived: "發現新貼文" sounds: "音效" sound: "音效" listen: "聆聽" @@ -550,19 +550,19 @@ sort: "排序" ascendingOrder: "昇冪" descendingOrder: "降冪" scratchpad: "暫存記憶體" -scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Misskey互動的结果。" +scratchpadDescription: "AiScript 控制台為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的结果。" output: "輸出" script: "腳本" -disablePagesScript: "停用頁面的AiScript腳本" +disablePagesScript: "停用頁面的 AiScript 腳本" updateRemoteUser: "更新遠端使用者資訊" deleteAllFiles: "刪除所有檔案" -deleteAllFilesConfirm: "要删除所有檔案嗎?" +deleteAllFilesConfirm: "要刪除所有檔案嗎?" removeAllFollowing: "解除所有追隨" removeAllFollowingDescription: "解除{host}所有的追隨。在伺服器不再存在時執行。" userSuspended: "該使用者已被停用" userSilenced: "該用戶已被禁言。" yourAccountSuspendedTitle: "帳戶已被凍結" -yourAccountSuspendedDescription: "由於違反了伺服器的服務條款或其他原因,該帳戶已被凍結。 您可以與管理員連繫以了解更多訊息。 請不要創建一個新的帳戶。" +yourAccountSuspendedDescription: "該帳戶已因違反伺服器服務條款或其他原因而被凍結。您可以向管理員查詢更多資訊。請不要建立新帳戶。" tokenRevoked: "權杖無效" tokenRevokedDescription: "登入權杖失效,請重新登入。" accountDeleted: "帳戶已被刪除" @@ -575,22 +575,22 @@ relays: "中繼" addRelay: "新增中繼" inboxUrl: "收件夾URL" addedRelays: "已加入的中繼" -serviceworkerInfo: "您需要啟用推送通知" -deletedNote: "已删除的貼文" -invisibleNote: "私密的貼文" +serviceworkerInfo: "您需要啟用推送通知。" +deletedNote: "已刪除的貼文" +invisibleNote: "隱藏的貼文" enableInfiniteScroll: "啟用自動滾動頁面模式" visibility: "可見性" poll: "投票" useCw: "隱藏內容" -enablePlayer: "打開播放器" +enablePlayer: "開啟播放器" disablePlayer: "關閉播放器" expandTweet: "展開推文" themeEditor: "主題編輯器" description: "描述" -describeFile: "添加標題 " -enterFileDescription: "輸入標題 " +describeFile: "新增標題" +enterFileDescription: "輸入標題" author: "作者" -leaveConfirm: "有未保存的更改。要放棄嗎?" +leaveConfirm: "尚未儲存修改。要放棄嗎?" manage: "管理" plugins: "外掛" preferencesBackups: "備份設定檔" @@ -616,14 +616,14 @@ enableEmail: "啟用發送電郵功能" emailConfigInfo: "用於確認電郵地址及密碼重置" email: "電子郵件" emailAddress: "電郵地址" -smtpConfig: "SMTP伺服器設定" +smtpConfig: "SMTP 伺服器設定" smtpHost: "主機" smtpPort: "埠" smtpUser: "使用者名稱" smtpPass: "密碼" emptyToDisableSmtpAuth: "留空使用者名稱和密碼以關閉SMTP驗證。" smtpSecure: "在 SMTP 連接中使用隱式 SSL/TLS" -smtpSecureInfo: "使用STARTTLS時關閉。" +smtpSecureInfo: "使用 STARTTLS 時關閉。" testEmail: "測試郵件發送" wordMute: "被靜音的文字" regexpError: "正規表達式錯誤" @@ -646,16 +646,16 @@ useGlobalSetting: "使用全域設定" useGlobalSettingDesc: "啟用時,將使用帳戶通知設定。停用時,則可以單獨設定。" other: "其他" regenerateLoginToken: "重新產生登入權杖" -regenerateLoginTokenDescription: "重新產生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦重產,所有裝置將會被登出。" +regenerateLoginTokenDescription: "重新產生用於登入的內部權杖。一般情況下是不需要這樣做的。重新產生後,所有裝置將會被登出。" setMultipleBySeparatingWithSpace: "您可以使用空格分隔多個項目。" -fileIdOrUrl: "檔案ID或URL" +fileIdOrUrl: "檔案 ID 或 URL" behavior: "行為" sample: "範例" abuseReports: "檢舉" reportAbuse: "檢舉" reportAbuseOf: "檢舉{name}" -fillAbuseReportDescription: "請填寫檢舉的詳細理由。可以的話,請附上針對的URL網址。" -abuseReported: "回報已送出。感謝您的報告。" +fillAbuseReportDescription: "請填寫檢舉的詳細理由。如有需要,請附上相關 URL。" +abuseReported: "檢舉完成。感謝您的報告。" reporter: "檢舉者" reporteeOrigin: "檢舉來源" reporterOrigin: "檢舉者來源" @@ -665,7 +665,7 @@ send: "發送" abuseMarkAsResolved: "處理完畢" openInNewTab: "在新分頁中開啟" openInSideView: "在側欄中開啟" -defaultNavigationBehaviour: "默認導航" +defaultNavigationBehaviour: "預設導航" editTheseSettingsMayBreakAccount: "修改這些設定可能會毀損您的帳戶" instanceTicker: "貼文的實例來源" waitingFor: "等待{x}" @@ -680,7 +680,7 @@ createNewClip: "建立新摘錄" unclip: "解除摘錄" confirmToUnclipAlreadyClippedNote: "此貼文已包含在摘錄「{name}」中。 你想將貼文從這個摘錄中排除嗎?" public: "公開" -i18nInfo: "Misskey已經被志願者們翻譯成各種語言版本,如果想要幫忙的話,可以進入{link}幫助翻譯。" +i18nInfo: "Misskey 已被志願者們翻譯成各種語言版本。您可以瀏覽{link}幫助翻譯。" manageAccessTokens: "管理存取權杖" accountInfo: "帳戶資訊" notesCount: "貼文數量" @@ -701,7 +701,7 @@ driveUsage: "雲端硬碟使用量" noCrawle: "拒絕搜尋引擎索引" noCrawleDescription: "要求網路搜尋引擎不要索引你的個人資料頁、貼文及頁面等。" lockedAccountInfo: "即使你通過了追隨者請求,除非你將貼文的可見性設定為 「追隨者」,否則任何人都能看見你的貼文。" -alwaysMarkSensitive: "默認將圖像/影像標記為敏感內容" +alwaysMarkSensitive: "預設將多媒體標記為敏感內容" loadRawImages: "以原始圖檔顯示附件圖檔的縮圖" disableShowingAnimatedImages: "不播放動態圖檔" verificationEmailSent: "已發送驗證電子郵件。請點擊進入電子郵件中的鏈接完成驗證。" @@ -738,7 +738,7 @@ myTheme: "我的佈景主題" backgroundColor: "背景" accentColor: "重點色彩" textColor: "文字" -saveAs: "另存為..." +saveAs: "另存新檔" advanced: "進階" advancedSettings: "進階設定" value: "數值" @@ -763,7 +763,7 @@ emailNotification: "郵件通知" publish: "發布" inChannelSearch: "頻道内搜尋" useReactionPickerForContextMenu: "點擊右鍵開啟反應工具欄" -typingUsers: "{users}輸入中..." +typingUsers: "{users}輸入中" jumpToSpecifiedDate: "跳轉到特定日期" showingPastTimeline: "顯示過往的時間線" clear: "清除" @@ -771,21 +771,21 @@ markAllAsRead: "全部標示為已讀" goBack: "返回" unlikeConfirm: "要取消按讚嗎?" fullView: "全熒幕顯示" -quitFullView: "退出全熒幕顯示" -addDescription: "添加描述" +quitFullView: "退出全螢幕顯示" +addDescription: "新增描述" userPagePinTip: "在貼文的選單中選擇\"置頂\",即可置頂該貼文至您的個人檔案頁面。" notSpecifiedMentionWarning: "此貼文有未指定的提及" info: "資訊" userInfo: "用戶資料" unknown: "未知" -onlineStatus: "在線狀態" -hideOnlineStatus: "隱藏在線狀態" -hideOnlineStatusDescription: "隱藏在線狀態後,可能會降低檢索等功能的便利性。" +onlineStatus: "上線狀態" +hideOnlineStatus: "隱藏上線狀態" +hideOnlineStatusDescription: "隱藏上線狀態後,可能會降低搜尋等功能的便利性。" online: "線上" active: "最近活躍" offline: "離線" notRecommended: "不推薦" -botProtection: "Bot防護" +botProtection: "Bot 防護" instanceBlocking: "已封鎖的實例" selectAccount: "選擇帳戶" switchAccount: "切換帳戶" @@ -796,11 +796,11 @@ user: "使用者" administration: "管理" accounts: "帳戶" switch: "切換" -noMaintainerInformationWarning: "尚未設定管理員信息。" -noBotProtectionWarning: "尚未設定Bot防護。" +noMaintainerInformationWarning: "尚未設定管理員訊息。" +noBotProtectionWarning: "尚未設定 Bot 防護。" configure: "設定" postToGallery: "發佈到相簿" -postToHashtag: "以此主題標籤發布" +postToHashtag: "以此主題標籤發佈" gallery: "相簿" recentPosts: "最新貼文" popularPosts: "熱門的貼文" @@ -835,7 +835,7 @@ accountDeletionInProgress: "正在刪除帳戶" usernameInfo: "在伺服器上您的帳戶是唯一的識別名稱。您可以使用字母 (a ~ z, A ~ Z)、數字 (0 ~ 9) 和下底線 (_)。之後帳戶名是不能更改的。" aiChanMode: "小藍模式" devMode: "開發者模式" -keepCw: "保持CW" +keepCw: "保持 CW" pubSub: "Pub/Sub 帳戶" lastCommunication: "最近的通信" resolved: "已解決" @@ -857,7 +857,7 @@ classic: "經典" muteThread: "將貼文串設為靜音" unmuteThread: "將貼文串的靜音解除" ffVisibility: "連繫的可見性" -ffVisibilityDescription: "您可以設定您的關注/關注者資訊的公開範圍" +ffVisibilityDescription: "您可以設定追隨或追隨者資訊的公開範圍" continueThread: "查看更多貼文" deleteAccountConfirm: "將要刪除帳戶。是否確定?" incorrectPassword: "密碼錯誤。" @@ -876,15 +876,15 @@ numberOfColumn: "列數" searchByGoogle: "搜尋" instanceDefaultLightTheme: "實例預設的淺色主題" instanceDefaultDarkTheme: "實例預設的深色主題" -instanceDefaultThemeDescription: "輸入物件形式的主题代碼" +instanceDefaultThemeDescription: "輸入物件形式的主題代碼" mutePeriod: "靜音的期限" period: "期限" indefinitely: "無期限" -tenMinutes: "10分鐘" -oneHour: "1小時" -oneDay: "1天" -oneWeek: "1週" -oneMonth: "1個月" +tenMinutes: "十分鐘" +oneHour: "一小時" +oneDay: "一天" +oneWeek: "一週" +oneMonth: "一個月" reflectMayTakeTime: "可能需要一些時間才會出現效果。" failedToFetchAccountInformation: "取得帳戶資訊失敗" rateLimitExceeded: "已超過速率限制" @@ -900,7 +900,7 @@ thereIsUnresolvedAbuseReportWarning: "有尚未處理的檢舉。" recommended: "推薦" check: "檢查" driveCapOverrideLabel: "更改這個使用者的雲端硬碟容量上限" -driveCapOverrideCaption: "如果指定0以下的值,就會被取消。" +driveCapOverrideCaption: "如果指定 0 以下的值,就會被取消。" requireAdminForView: "必須以管理員帳戶登入才可以檢視。" isSystemAccount: "由系統自動建立與管理的帳戶。" typeToConfirm: "要執行這項操作,請輸入 {x} " @@ -927,21 +927,21 @@ failedToUpload: "上傳失敗" cannotUploadBecauseInappropriate: "由於判定可能包含不適當的內容,因此無法上傳。" cannotUploadBecauseNoFreeSpace: "由於雲端硬碟沒有可用空間,因此無法上傳。" cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制,無法上傳。" -beta: "Beta" -enableAutoSensitive: "自動NSFW判定" -enableAutoSensitiveDescription: "如果可用,請利用機器學習在媒體上自動設置 NSFW 旗標。 即使關閉此功能,依實例而定也可能會自動設置。" -activeEmailValidationDescription: "積極地驗證用戶的電子郵件地址,判斷它是否為免洗地址,或者它是否可以通信。 若關閉,則只會檢查字元是否正確。" +beta: "測試版" +enableAutoSensitive: "自動 NSFW 判定" +enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷多媒體內容是否需要標記 NSFW。即使關閉此功能,也可能會依實例規則而自動啟用。" +activeEmailValidationDescription: "積極驗證帳戶的電郵地址,以判斷它是否可以通訊。關閉此選項代表只會檢查地址是否符合格式。" navbar: "導覽列" shuffle: "隨機" account: "帳戶" move: "移動 " pushNotification: "推播通知" subscribePushNotification: "啟用推播通知" -unsubscribePushNotification: "停止推播通知" +unsubscribePushNotification: "停用推播通知" pushNotificationAlreadySubscribed: "推播通知啟用中" pushNotificationNotSupported: "瀏覽器或實例不支援推播通知" -sendPushNotificationReadMessage: "通知與訊息如果已讀的話,就將推播通知刪除" -sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」通知將立刻顯示。可能會增加設備的電池消耗。" +sendPushNotificationReadMessage: "如果已閱讀通知與訊息,就刪除推播通知" +sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」通知將立刻顯示。可能會更消耗裝置電池。" windowMaximize: "最大化" windowMinimize: "最小化" windowRestore: "復原" @@ -956,8 +956,8 @@ numberOfLikes: "讚數" show: "檢視" neverShow: "不再顯示" remindMeLater: "以後再說" -didYouLikeMisskey: "您是否喜愛Misskey呢?" -pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發能夠持續!" +didYouLikeMisskey: "您喜歡 Misskey 嗎?" +pleaseDonate: "Misskey 是由 {host} 使用的免費軟體。請贊助我們,讓開發得以持續!" roles: "角色" role: "角色" noRole: "沒有角色" @@ -969,23 +969,23 @@ color: "顏色" manageCustomEmojis: "管理自訂表情符號" youCannotCreateAnymore: "您無法再建立更多了。" cannotPerformTemporary: "暫時無法進行" -cannotPerformTemporaryDescription: "由於超過操作次數限制,暫時無法進行。請過一段時間之後再嘗試。" +cannotPerformTemporaryDescription: "由於超過操作次數限制,因此暫時無法進行。請稍後再嘗試。" invalidParamError: "參數錯誤" -invalidParamErrorDescription: "請求參數有問題。通常是bug造成的,但也有輸入的字元數過多之類的可能性。" +invalidParamErrorDescription: "請求參數有問題。這可能是漏洞或輸入過多字元所致。" permissionDeniedError: "操作被拒絕" -permissionDeniedErrorDescription: "本帳號沒有執行這個操作的權限。" +permissionDeniedErrorDescription: "此帳戶沒有執行這個操作的權限。" preset: "預設值" selectFromPresets: "從預設值中選擇" achievements: "成就" gotInvalidResponseError: "伺服器的回應無效" gotInvalidResponseErrorDescription: "伺服器可能已關閉或者在維護中,請稍後再試。" thisPostMayBeAnnoying: "這篇貼文可能會造成別人的困擾。" -thisPostMayBeAnnoyingHome: "發布到首頁" +thisPostMayBeAnnoyingHome: "發佈到首頁" thisPostMayBeAnnoyingCancel: "退出" -thisPostMayBeAnnoyingIgnore: "直接發布貼文" +thisPostMayBeAnnoyingIgnore: "直接發佈貼文" collapseRenotes: "省略顯示已看過的轉發貼文" internalServerError: "內部伺服器錯誤" -internalServerErrorDescription: "內部伺服器發生了非預期的錯誤。" +internalServerErrorDescription: "內部伺服器出現意外錯誤。" copyErrorInfo: "複製錯誤資訊" joinThisServer: "在此伺服器上註冊" exploreOtherServers: "探索其他伺服器" @@ -995,7 +995,7 @@ disableFederationConfirmWarn: "即使停止了聯邦功能,貼文也不會變 disableFederationOk: "停止聯邦功能" invitationRequiredToRegister: "目前這個伺服器為邀請制,必須擁有邀請碼才能註冊。" emailNotSupported: "這個伺服器不支援寄送郵件" -postToTheChannel: "發布到頻道" +postToTheChannel: "發佈到頻道" cannotBeChangedLater: "之後不能變更。" reactionAcceptance: "接受表情反應" likeOnly: "僅限讚" @@ -1006,7 +1006,7 @@ rolesAssignedToMe: "指派給自己的角色" resetPasswordConfirm: "重設密碼?" sensitiveWords: "敏感詞" sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。" -sensitiveWordsDescription2: "用空格分隔關鍵詞構成AND格式,用斜線包圍關鍵字構成正規表達式。" +sensitiveWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。" notesSearchNotAvailable: "無法使用搜尋貼文功能。" license: "授權" unfavoriteConfirm: "要取消收錄我的最愛嗎?" @@ -1017,8 +1017,8 @@ retryAllQueuesConfirmTitle: "要現在重試嗎?" retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。" enableChartsForRemoteUser: "生成遠端用戶的圖表" enableChartsForFederatedInstances: "生成遠端伺服器的圖表" -showClipButtonInNoteFooter: "將摘錄添加至貼文" -largeNoteReactions: "將貼文的反應放大顯示" +showClipButtonInNoteFooter: "新增摘錄至貼文" +largeNoteReactions: "放大顯示貼文反應" noteIdOrUrl: "貼文ID或URL" video: "影片" videos: "影片" @@ -1046,33 +1046,33 @@ pleaseConfirmBelowBeforeSignup: "在本伺服器註冊之前,請確認下列 pleaseAgreeAllToContinue: "必須全部勾選「同意」才能繼續。" continue: "繼續" preservedUsernames: "保留的使用者名稱" -preservedUsernamesDescription: "換行列舉要保留的使用者名稱。此處指定的使用者名稱,在建立帳戶時無法使用,但由管理者所建立的帳戶不受此限。此外,既有的帳戶也不受影響。" +preservedUsernamesDescription: "換行列舉要保留的使用者名稱。此處出現的名稱將在註冊時禁用,但由管理者建立帳戶則不受此限。此外,既有的帳戶也不受影響。" createNoteFromTheFile: "由此檔案建立貼文" archive: "封存" channelArchiveConfirmTitle: "要封存{name}嗎?" -channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。" +channelArchiveConfirmDescription: "封存後,將不會在頻道列表與搜尋結果中顯示,也無法發佈新貼文。" thisChannelArchived: "這個頻道已被封存。" displayOfNote: "顯示貼文" initialAccountSetting: "初始設定" youFollowing: "追隨中" preventAiLearning: "拒絕接受生成式AI的訓練" -preventAiLearningDescription: "要求外部的文章生成式AI或圖像生成式AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的,但不能完全防止AI的學習,因為這要看該AI是否遵守這個要求。" +preventAiLearningDescription: "要求站外生成式 AI 不使用您發佈的內容訓練模型。此功能會使伺服器於 HTML 回應新增「noai」標籤,而因為要視乎 AI 會否遵守該標籤,所以此功能無法完全阻止所有 AI 使用您的內容。" options: "選項" specifyUser: "指定使用者" failedToPreviewUrl: "無法預覽" update: "更新" -rolesThatCanBeUsedThisEmojiAsReaction: "可以用這個做為反應的角色" -rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "未指定角色的情況,則任何人都可以將它用做反應。" -rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "角色必須是公開的角色。" -cancelReactionConfirm: "要取消做出的反應嗎?" -changeReactionConfirm: "要變更做出的反應嗎?" +rolesThatCanBeUsedThisEmojiAsReaction: "可以使用此表情符號為反應的角色" +rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "如沒有指定角色,任何人都可使用此表情回應。" +rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "必須為公開角色。" +cancelReactionConfirm: "要取消此反應嗎?" +changeReactionConfirm: "要更改反應嗎?" later: "稍後再說" -goToMisskey: "往Misskey" +goToMisskey: "往 Misskey" additionalEmojiDictionary: "表情符號的附加辭典" installed: "已安裝" branding: "品牌宣傳" -enableServerMachineStats: "公布伺服器的機器資訊" -enableIdenticonGeneration: "啟用每個使用者的Identicon" +enableServerMachineStats: "公佈伺服器的機器資訊" +enableIdenticonGeneration: "啟用生成使用者的 Identicon " turnOffToImprovePerformance: "關閉時會提高性能。" createInviteCode: "建立邀請碼" createWithOptions: "使用選項建立" @@ -1091,6 +1091,9 @@ usedAt: "使用的日期和時間" unused: "未使用" used: "已使用" expired: "過期" +doYouAgree: "你同意嗎?" +beSureToReadThisAsItIsImportant: "重要,請務必閱讀。" +iHaveReadXCarefullyAndAgree: "我已仔細閱讀並同意「{x}」的内容。" _initialAccountSetting: accountCreated: "帳戶已建立完成!" letsStartAccountSetup: "來進行帳戶的初始設定吧。" @@ -1107,7 +1110,7 @@ _initialAccountSetting: skipAreYouSure: "要略過初始設定嗎?" laterAreYouSure: "稍後再重新進行初始設定嗎?" _serverRules: - description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。" + description: "設定在註冊頁面顯示的伺服器簡要規則。建議是服務條款的摘要。" _accountMigration: moveFrom: "從其他帳戶遷移到這個帳戶" moveFromSub: "為另一個帳戶建立別名" @@ -1121,111 +1124,111 @@ _accountMigration: startMigration: "遷移" migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。" movedAndCannotBeUndone: "帳戶已遷移。\n遷移無法撤消。" - postMigrationNote: "在遷移操作後的24小時之後解除此帳戶的追隨。此帳戶的追隨中、追隨者數量變為0。由於不會解除追隨者,你的追隨者仍然可以繼續檢視這個帳戶發布給追隨者的貼文。" + postMigrationNote: "在完成遷移的 24 小時後解除此帳戶的追隨。此帳戶的追隨中、追隨者數量變為 0。由於不會解除追隨者,你的追隨者仍然可以繼續檢視這個帳戶發布給追隨者的貼文。" movedTo: "要遷移到的帳戶:" _achievements: earnedAt: "獲得日期" _types: _notes1: - title: "just setting up my msky" + title: "歡迎!" description: "發出了第一則貼文" - flavor: "祝您的Misskey生活愉快!" + flavor: "祝您的 Misskey 生活愉快!" _notes10: title: "若干貼文" - description: "發表了10則貼文" + description: "發佈了十篇貼文" _notes100: title: "許多貼文" - description: "發表了100則貼文" + description: "發佈了一百篇貼文" _notes500: title: "滿滿的貼文" - description: "發表了500則貼文" + description: "發佈了五百篇貼文" _notes1000: title: "堆積如山的貼文" - description: "發表了1000則貼文" + description: "發佈了一千篇貼文" _notes5000: title: "滔滔不絕的貼文" - description: "發表了5000則貼文" + description: "發佈了五千篇貼文" _notes10000: title: "超級貼文" - description: "發表了10000則貼文" + description: "發佈了一萬篇貼文" _notes20000: - title: "需要更多的貼文" - description: "發表了20000則貼文" + title: "需要更多貼文" + description: "發佈了兩萬篇貼文" _notes30000: title: "貼文貼文貼文" - description: "發表了30000則貼文" + description: "發佈了三萬篇貼文" _notes40000: title: "貼文工廠" - description: "發表了40000則貼文" + description: "發佈了四萬篇貼文" _notes50000: title: "貼文星球" - description: "發表了50000則貼文" + description: "發佈了五萬篇貼文" _notes60000: title: "貼文類星體" - description: "發表了60000則貼文" + description: "發佈了六萬篇貼文" _notes70000: title: "貼文黑洞" - description: "發表了70000則貼文" + description: "發佈了七萬篇貼文" _notes80000: title: "貼文銀河" - description: "發表了80000則貼文" + description: "發佈了八萬篇貼文" _notes90000: title: "貼文宇宙" - description: "發表了90000則貼文" + description: "發佈了九萬篇貼文" _notes100000: title: "ALL YOUR NOTE ARE BELONG TO US" - description: "發表了100,000則貼文" + description: "發佈了十萬篇貼文" flavor: "有這麼多東西要寫嗎?" _login3: title: "初學者Ⅰ" - description: "總登入天數為3天" - flavor: "從今天開始,我就是Misskist" + description: "總登入天數為三天" + flavor: "從今天開始,我就是 Misskist" _login7: title: "初學者ⅠⅠ" - description: "總登入天數為7天" + description: "總登入天數為七天" flavor: "您開始習慣了嗎?" _login15: title: "初學者ⅠⅠⅠ" - description: "總登入天數為15天" + description: "總登入天數為十五天" _login30: title: "Misskist Ⅰ" - description: "總登入天數為30天" + description: "總登入天數為三十天" _login60: title: "Misskist ⅠⅠ" - description: "總登入天數為60天" + description: "總登入天數為六十天" _login100: title: "Misskist ⅠⅠⅠ" - description: "總登入天數為100天" - flavor: "辣個 Misskist 用戶" + description: "總登入天數為一百天" + flavor: "凶暴的 Misskist" _login200: title: "普通Ⅰ" - description: "總登入天數為200天" + description: "總登入天數為兩百天" _login300: - title: "普通IⅠ" - description: "總登入天數為300天" + title: "普通ⅠⅠ" + description: "總登入天數為三百天" _login400: - title: "普通IIⅠ" - description: "總登入天數為400天" + title: "普通ⅠⅠⅠ" + description: "總登入天數為四百天" _login500: title: "老兵Ⅰ" - description: "總登入天數為500天" + description: "總登入天數為五百天" flavor: "諸君,我喜歡貼文" _login600: title: "老兵ⅠⅠ" - description: "總登入天數為600天" + description: "總登入天數為六百天" _login700: title: "老兵ⅠⅠⅠ" - description: "總登入天數為700天" + description: "總登入天數為七百天" _login800: title: "貼文大師Ⅰ" - description: "總登入天數為800天" + description: "總登入天數為八百天" _login900: title: "貼文大師ⅠⅠ" - description: "總登入天數為900天" + description: "總登入天數為九百天" _login1000: title: "貼文大師ⅠⅠⅠ" - description: "總登入天數為1,000天" - flavor: "感謝您使用Misskey!" + description: "總登入天數為一千天" + flavor: "感謝您使用 Misskey!" _noteClipped1: title: "忍不住要收進摘錄裡" description: "第一次將貼文收進摘錄" @@ -1252,7 +1255,7 @@ _achievements: title: "朋友很多" description: "追隨超過50人了" _following100: - title: "100位朋友" + title: "一百位朋友" description: "追隨超過100人了" _following300: title: "朋友過多" @@ -1274,20 +1277,20 @@ _achievements: description: "追隨者超過300人了" _followers500: title: "基地台" - description: "超過500名追隨者了" + description: "超過五百名追隨者了" _followers1000: title: "影響者" - description: "超過1000名追隨者了" + description: "超過一千名追隨者了" _collectAchievements30: title: "成就收藏家" - description: "獲得30個以上的成就" + description: "獲得三十個以上的成就" _viewAchievements3min: title: "喜愛成就" - description: "看成就列表要花3分鐘以上" + description: "看成就列表要花三分鐘以上" _iLoveMisskey: title: "I Love Misskey" - description: "發布「I ❤ #Misskey」" - flavor: "感謝您使用Misskey! by 開發團隊" + description: "發佈「I ❤ #Misskey」" + flavor: "感謝您使用 Misskey!by 開發團隊" _foundTreasure: title: "尋寶" description: "發現了隱藏的寶藏" @@ -1410,7 +1413,7 @@ _role: pinMax: "置頂貼文的最大數量" antennaMax: "可建立的天線數量" wordMuteMax: "靜音文字的最大字數" - webhookMax: "可建立的Webhook數量" + webhookMax: "可建立的 Webhook 數量" clipMax: "可建立的摘錄數量" noteEachClipsMax: "摘錄內貼文的最大數量" userListMax: "可建立的使用者清單數量" @@ -1428,21 +1431,21 @@ _role: followersMoreThanOrEq: "追隨者人數在~以上" followingLessThanOrEq: "追隨人數在~以下" followingMoreThanOrEq: "追隨人數在~以上" - notesLessThanOrEq: "發布數在~以下" - notesMoreThanOrEq: "發布數在~以上" - and: "~和~" + notesLessThanOrEq: "貼文數在~以下" + notesMoreThanOrEq: "貼文數在~以上" + and: "~及~" or: "~或~" not: "~否" _sensitiveMediaDetection: description: "您可以使用機器學習自動檢測敏感媒體並將其用於審查。 伺服器的負荷會稍微增加。" sensitivity: "檢測敏感度" sensitivityDescription: "敏感度低時,誤檢測(偽陽性)會減少。敏感度高時,漏檢(偽陰性)會減少。" - setSensitiveFlagAutomatically: "設定 NSFW 旗標" + setSensitiveFlagAutomatically: "設定 NSFW 標籤" setSensitiveFlagAutomaticallyDescription: "即使將此設定關閉,判定結果也會保留在內部。" analyzeVideos: "啟用影片分析" analyzeVideosDescription: "除了靜止影像以外,也分析影片。伺服器的負荷會稍微增加。" _emailUnavailable: - used: "已經在使用中" + used: "已被使用" format: "格式無效" disposable: "不是永久可用的地址" mx: "郵件伺服器不正確" @@ -1454,11 +1457,11 @@ _ffVisibility: _signup: almostThere: "即將完成" emailAddressInfo: "請輸入您所使用的電子郵件地址。電子郵件地址不會被公開。" - emailSent: "已將確認郵件發送至您輸入的電子郵件地址 ({email})。請開啟電子郵件中的連結以完成帳戶創建。" + emailSent: "已發送確認郵件至您輸入的電子郵件地址({email})。請開啟電子郵件中的連結完成註冊。" _accountDelete: accountDelete: "刪除帳戶" - mayTakeTime: "刪除帳戶的處理負荷較大,如果帳戶產生的內容數量上傳的檔案數量較多的話,就需要花费一段時間才能完成。" - sendEmail: "帳戶删除完成後,將向註冊地電子郵件地址發送通知。" + mayTakeTime: "刪除帳戶的處理負荷較大,如果帳戶發佈的內容以及上傳的檔案數量較多,則需要一段時間才能完成。" + sendEmail: "帳戶刪除完成後,將向其電子郵件地址發送通知。" requestAccountDelete: "刪除帳戶請求" started: "已開始刪除作業。" inProgress: "正在刪除" @@ -1474,8 +1477,8 @@ _forgotPassword: _gallery: my: "我的貼文" liked: "喜歡的貼文" - like: "讚" - unlike: "收回喜歡" + like: "讚好" + unlike: "收回讚好" _email: _follow: title: "您有新的追隨者" @@ -1483,7 +1486,7 @@ _email: title: "收到追隨請求" _plugin: install: "安裝外掛組件" - installWarn: "請不要安裝來源不明的外掛組件。" + installWarn: "請不要安裝來源不明的外掛。" manage: "管理外掛" _preferencesBackups: list: "已備份的設定檔" @@ -1493,7 +1496,7 @@ _preferencesBackups: save: "覆蓋存檔" inputName: "輸入備份檔名稱" cannotSave: "無法儲存" - nameAlreadyExists: "備份檔名稱「{name}」已經存在。請指定不同的名稱。" + nameAlreadyExists: "備份檔名稱「{name}」已經存在。請填寫其他名稱。" applyConfirm: "將備份檔「{name}」套用在現在的裝置嗎?現在的裝置設定將會消失。" saveConfirm: "要覆蓋存檔{name}嗎?" deleteConfirm: "要刪除{name}嗎?" @@ -1514,14 +1517,14 @@ _aboutMisskey: contributors: "主要貢獻者" allContributors: "全體貢獻人員" source: "原始碼" - translation: "翻譯Misskey" - donate: "贊助Misskey" + translation: "翻譯 Misskey" + donate: "贊助 Misskey" morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰" patrons: "贊助者" _displayOfSensitiveMedia: - respect: "隱藏設定為敏感的媒體" - ignore: "不隱藏設定為敏感的媒體" - force: "隱藏所有媒體" + respect: "隱藏被標記為敏感的多媒體內容" + ignore: "不隱藏被標記為敏感的多媒體內容" + force: "隱藏所有多媒體內容" _instanceTicker: none: "隱藏" remote: "向遠端使用者顯示" @@ -1549,17 +1552,17 @@ _menuDisplay: hide: "隱藏" _wordMute: muteWords: "加入靜音文字" - muteWordsDescription: "用空格分隔指定AND,用換行分隔指定OR。" - muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。" - softDescription: "隱藏時間軸中指定條件的貼文。" - hardDescription: "具有指定條件的貼文將不添加到時間軸。 即使您更改條件,未被添加的貼文也會被排除在外。" + muteWordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)。" + muteWordsDescription2: "用斜線包圍關鍵字代表正規表達式。" + softDescription: "隱藏時間軸中符合特定條件的貼文。" + hardDescription: "符合特定條件的貼文將不會新增至時間軸。 即使您更改條件,未被新增的貼文也會被排除在外。" soft: "軟性靜音" hard: "硬性靜音" mutedNotes: "已靜音的貼文" _instanceMute: instanceMuteDescription: "包括對被靜音實例上的用戶的回覆,被設定的實例上所有貼文及轉發都會被靜音。" - instanceMuteDescription2: "設定時以換行進行分隔" - title: "被設定的實例,貼文將被隱藏。" + instanceMuteDescription2: "換行以分隔" + title: "將隱藏被設定的實例貼文。" heading: "將實例靜音" _theme: explore: "取得佈景主題" @@ -1584,13 +1587,13 @@ _theme: func: "函数" funcKind: "功能類型" argument: "參數" - basedProp: "要基於的屬性的名稱 " + basedProp: "基於的屬性名稱 " alpha: "透明度" darken: "暗度" lighten: "亮度" - inputConstantName: "請輸入常數的名稱" + inputConstantName: "請輸入常數名稱" importInfo: "您可以在此貼上主題代碼,將其匯入編輯器中" - deleteConstantConfirm: "確定要删除常數{const}嗎?" + deleteConstantConfirm: "確定要刪除常數{const}嗎?" keys: accent: "重點色彩" bg: "背景" @@ -1598,14 +1601,14 @@ _theme: focus: "聚焦" indicator: "指標" panel: "面板" - shadow: "陰影" + shadow: "影子" header: "標題" navBg: "側邊欄的背景 " navFg: "側邊欄的文字" - navHoverFg: "側邊欄文字(懸停) " - navActive: "側邊欄文本 (活動)" + navHoverFg: "側邊欄文字(懸浮) " + navActive: "側邊欄文字(活動)" navIndicator: "側邊欄指示符" - link: "鏈接" + link: "連結" hashtag: "標籤" mention: "提到" mentionMe: "提到了我" @@ -1613,15 +1616,15 @@ _theme: modalBg: "對話框背景" divider: "分割線" scrollbarHandle: "捲動條" - scrollbarHandleHover: "捲動條 (漂浮)" + scrollbarHandleHover: "捲動條(懸浮)" dateLabelFg: "日期標籤文字" infoBg: "資訊背景" infoFg: "資訊內容" infoWarnBg: "警告背景" - infoWarnFg: "警告字元" + infoWarnFg: "警告文字" cwBg: "CW 按鈕背景" - cwFg: "CW 按鈕文本" - cwHoverBg: "CW 按鈕背景 (漂浮)" + cwFg: "CW 按鈕文字" + cwHoverBg: "CW 按鈕背景(懸浮)" toastBg: "通知背景" toastFg: "通知文本" buttonBg: "按鈕背景" @@ -1630,11 +1633,11 @@ _theme: listItemHoverBg: "列表物品背景 (漂浮)" driveFolderBg: "雲端硬碟文件夾背景" wallpaperOverlay: "壁紙覆蓋層" - badge: "獎章" + badge: "徽章" messageBg: "私訊背景" - accentDarken: "強調色(偏暗)" - accentLighten: "強調色(明亮)" - fgHighlighted: "高亮顯示文本" + accentDarken: "強調色(黑暗)" + accentLighten: "強調色(明亮)" + fgHighlighted: "突顯文字" _sfx: note: "貼文" noteMy: "我的貼文" @@ -1650,7 +1653,7 @@ _ago: minutesAgo: "{n}分鐘前" hoursAgo: "{n}小時前" daysAgo: "{n}天前" - weeksAgo: "{n}周前" + weeksAgo: "{n}週前" monthsAgo: "{n}個月前" yearsAgo: "{n}年前" invalid: "未發現" @@ -1660,33 +1663,33 @@ _time: hour: "小時" day: "日" _timelineTutorial: - title: "Misskey的使用方法" - step1_1: "這個畫面是「時間軸」。發布到{name}的「貼文」會按照時間順序顯示。" - step1_2: "時間軸有多種類型,例如在「首頁時間軸」中流動的是您追蹤的人的貼文;而在「本地時間軸」流動的是{name}全體的貼文。" - step2_1: "試試看,發布個貼文吧!按畫面上鉛筆圖示的按鈕開啟表格。" - step2_2: "初次貼文的內容,建議包括自我介紹以及「開始使用{name}」。" + title: "Misskey 的使用方法" + step1_1: "這個畫面是「時間軸」。發佈到{name}的「貼文」會按照時間順序顯示。" + step1_2: "時間軸有多種類型,例如「首頁時間軸」是您追蹤帳戶的貼文、「本地時間軸」是{name}內所有帳戶的貼文。" + step2_1: "不如現在就嘗試發文吧!按鉛筆圖示的按鈕開啟發文頁面。" + step2_2: "您可以在第一篇貼文裡寫自我介紹,或是「我來到 {name} 了」之類的話。" step3_1: "貼文發出去了嗎?" - step3_2: "如果你的貼文出現在時間軸上,就代表發文成功。" + step3_2: "如果您的貼文出現在時間軸上,就代表發文成功。" step4_1: "可以對貼文標記「反應」。" - step4_2: "點擊貼文的「+」圖示,即可選擇喜好的表情符號來標記反應。" + step4_2: "點擊貼文的「+」圖示,即可選擇表情符號來反應。" _2fa: - alreadyRegistered: "此設備已經被註冊過了" + alreadyRegistered: "此裝置已被註冊過了" registerTOTP: "開始設定驗證應用程式" passwordToTOTP: "請輸入密碼" - step1: "首先,在您的設備上安裝二步驗證程式,例如{a}或{b}。" - step2: "然後,掃描螢幕上的QR code。" - step2Click: "點擊QR code,可以使用設備上安裝的驗證應用程式或金鑰環進行註冊。" - step2Url: "在桌面版應用中,請輸入以下的URL:" + step1: "首先,在您的裝置上安裝驗證程式,例如 {a} 或 {b}。" + step2: "然後,掃描螢幕上的 QR 碼。" + step2Click: "您可以點擊 QR 碼,以使用裝置上的驗證應用程式或金鑰環註冊。" + step2Url: "請在桌面版應用程式中輸入以下的 URL:" step3Title: "輸入驗證碼" - step3: "輸入您的App提供的權杖以完成設定。" + step3: "輸入應用程式所提供的權杖以完成設定。" step4: "從現在開始,任何登入操作都將要求您提供權杖。" securityKeyNotSupported: "您的瀏覽器不支援安全金鑰。" - registerTOTPBeforeKey: "要註冊安全金鑰・Passkey,請先設定驗證應用程式。" - securityKeyInfo: "您可以設定使用支援FIDO2的硬體安全鎖、終端設備的指纹認證或者PIN碼來登入。" - chromePasskeyNotSupported: "目前不支援Chrome的Passkey。" - registerSecurityKey: "註冊安全金鑰・Passkey" + registerTOTPBeforeKey: "如要註冊安全金鑰或 Passkey,請先設定驗證應用程式。" + securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全鎖、終端設備的指紋認證,或者 PIN 碼來登入。" + chromePasskeyNotSupported: "目前不支援 Chrome 的 Passkey。" + registerSecurityKey: "註冊安全金鑰或 Passkey" securityKeyName: "輸入金鑰名稱" - tapSecurityKey: "按照瀏覽器的說明操作,註冊安全金鑰和Passkey。" + tapSecurityKey: "按照瀏覽器的說明註冊安全金鑰或 Passkey。" removeKey: "刪除安全金鑰" removeKeyConfirm: "要刪除{name}嗎?" whyTOTPOnlyRenew: "如果註冊了安全金鑰,則無法解除驗證應用程式的設定。" @@ -1704,9 +1707,9 @@ _permissions: "read:favorites": "瀏覽我的最愛" "write:favorites": "編輯我的最愛列表" "read:following": "查看追隨中的用戶資訊" - "write:following": "追隨/解除追隨" + "write:following": "追隨/解除追隨" "read:messaging": "顯示訊息" - "write:messaging": "撰寫或刪除私人訊息" + "write:messaging": "撰寫或刪除訊息" "read:mutes": "顯示已靜音列表" "write:mutes": "編輯已靜音列表" "write:notes": "撰寫或刪除貼文" @@ -1759,12 +1762,12 @@ _widgets: calendar: "行事曆" trends: "發燒貼文" clock: "時鐘" - rss: "RSS閱讀器" - rssTicker: "RSS跑馬燈" + rss: "RSS 閱讀器" + rssTicker: "RSS 跑馬燈" activity: "動態" photos: "照片" digitalClock: "電子時鐘" - unixClock: "UNIX時間" + unixClock: "UNIX 時間" federation: "站台聯邦" instanceCloud: "實例雲" postForm: "發佈窗口" @@ -1772,8 +1775,8 @@ _widgets: button: "按鈕" onlineUsers: "線上的用戶" jobQueue: "佇列" - serverMetric: "服務器指標 " - aiscript: "AiScript控制台" + serverMetric: "伺服器指標 " + aiscript: "AiScript 控制台" aiscriptApp: "AiScript App" aichan: "小藍" userList: "使用者列表" @@ -1783,33 +1786,33 @@ _widgets: _cw: hide: "隱藏" show: "瀏覽更多" - chars: "{count}字元" + chars: "{count} 個字元" files: "{count} 個檔案" _poll: noOnlyOneChoice: "至少需要兩個選項。" - choiceN: "選擇{n}" + choiceN: "選擇 {n}" noMore: "沒辦法再添加選項了" canMultipleVote: "可以多次投票" expiration: "期限" infinite: "無期限" at: "結束時間" - after: "進度指定 " + after: "指定時效" deadlineDate: "截止日期" deadlineTime: "小時" duration: "時長" - votesCount: "{n}票" - totalVotes: "一共{n}票" + votesCount: "{n} 票" + totalVotes: "合共 {n} 票" vote: "投票" showResult: "顯示結果" voted: "已投票" closed: "已結束" - remainingDays: "{d}天{h}小時後結束" - remainingHours: "{h}小時{m}分後結束" - remainingMinutes: "{m}分{s}秒後結束" - remainingSeconds: "{s}秒後截止" + remainingDays: "{d} 天 {h} 小時後結束" + remainingHours: "{h} 小時 {m} 分後結束" + remainingMinutes: "{m} 分 {s} 秒後結束" + remainingSeconds: "{s} 秒後截止" _visibility: public: "公開" - publicDescription: "發布給所有用戶 " + publicDescription: "發佈給所有帳戶" home: "首頁" homeDescription: "僅發布至首頁的時間軸" followers: "追隨者" @@ -1823,12 +1826,12 @@ _postForm: quotePlaceholder: "引用此貼文..." channelPlaceholder: "發佈到頻道" _placeholders: - a: "今天過得如何?" - b: "有什麼新鮮事嗎?" + a: "今天過得如何?" + b: "有什麼新鮮事嗎?" c: "有什麼新鮮想法嗎?" - d: "想要發布些什麼嗎?" - e: "寫些什麼吧..." - f: "期待你發佈的內容..." + d: "想要發佈些什麼嗎?" + e: "寫些什麼吧……" + f: "靜待發文……" _profile: name: "名稱" username: "使用者名稱" @@ -1853,45 +1856,45 @@ _exportOrImport: _charts: federation: "站台聯邦" apRequest: "請求" - usersIncDec: "使用者増減" + usersIncDec: "使用者增減" usersTotal: "使用者合共" activeUsers: "活躍使用者" notesIncDec: "貼文増減" localNotesIncDec: "本地貼文増減" remoteNotesIncDec: "遠端貼文數目增减" notesTotal: "貼文合共" - filesIncDec: "檔案増減" - filesTotal: "累計檔案" - storageUsageIncDec: "儲存空間的増減" - storageUsageTotal: "已使用的儲存空間合共" + filesIncDec: "檔案增減" + filesTotal: "檔案總數" + storageUsageIncDec: "儲存空間增減" + storageUsageTotal: "儲存空間用量" _instanceCharts: requests: "請求" - users: "使用者増減" - usersTotal: "總計使用者" - notes: "貼文増減" + users: "使用者增減" + usersTotal: "使用者總數" + notes: "貼文增減" notesTotal: "累計貼文" - ff: "追隨/追隨者的増減" - ffTotal: "追隨/追隨者累計" - cacheSize: "增加或減少快取用量" - cacheSizeTotal: "快取大小總計" - files: "檔案數量的増減" - filesTotal: "檔案數量總計" + ff: "追隨/追隨者增減" + ffTotal: "追隨/追隨者總數" + cacheSize: "快取用量增減" + cacheSizeTotal: "快取用量總數" + files: "檔案總數增減" + filesTotal: "檔案總數累計" _timelines: home: "首頁" local: "本地" social: "社交" global: "公開" _play: - new: "新增Play" - edit: "編輯Play" - created: "已新增Play" - updated: "已更新Play" - deleted: "已刪除Play" + new: "新增 Play" + edit: "編輯 Play" + created: "已新增Play " + updated: "已更新Play " + deleted: "已刪除 Play" pageSetting: "Play設定" - editThisPage: "編輯這個Play" + editThisPage: "編輯此 Play" viewSource: "檢視原始碼" - my: "自己的Play" - liked: "按了讚的Play" + my: "自己的 Play" + liked: "按讚的 Play" featured: "人氣" title: "標題" script: "腳本" @@ -1904,16 +1907,16 @@ _pages: updated: "頁面已更新" deleted: "頁面已被刪除" pageSetting: "頁面設定" - nameAlreadyExists: "指定的頁面URL已經存在" - invalidNameTitle: "指定的頁面URL無效" + nameAlreadyExists: "該頁面 URL 已存在" + invalidNameTitle: "無效的頁面 URL" invalidNameText: "請確定是否為非空白" editThisPage: "編輯此頁面" viewSource: "檢視原始碼" viewPage: "顯示頁面" - like: "喜歡" - unlike: "收回喜歡" + like: "讚好" + unlike: "收回讚好" my: "我的頁面" - liked: "已喜歡的頁面" + liked: "已讚好的頁面" featured: "人氣" inspector: "面板檢查" contents: "內容" @@ -1935,7 +1938,7 @@ _pages: inputBlocks: "輸入" specialBlocks: "特殊" blocks: - text: "字串" + text: "文字" textarea: "字串區域" section: "區段" image: "圖片" @@ -1976,7 +1979,7 @@ _notification: achievementEarned: "獲得成就" app: "應用程式通知" _actions: - followBack: "回關" + followBack: "追隨回去" reply: "回覆" renote: "轉發" _deck: @@ -1993,10 +1996,10 @@ _deck: profile: "個人檔案" newProfile: "新建個人檔案" deleteProfile: "刪除個人檔案" - introduction: "組合欄位來製作屬於自己的介面吧!" - introduction2: "您可以隨時透過按畫面右方的 + 來添加欄位。" - widgetsIntroduction: "請從欄位的選單中,選擇「編輯小工具」來添加小工具" - useSimpleUiForNonRootPages: "用簡易UI顯示非根頁面" + introduction: "組合多個欄位,製作屬於自己的介面吧!" + introduction2: "您可以隨時按畫面右方的「+」新增欄位。" + widgetsIntroduction: "請從欄位選單中選擇「編輯小工具」新增小工具。" + useSimpleUiForNonRootPages: "用簡易 UI 顯示非根頁面" _columns: main: "主列" widgets: "小工具" @@ -2009,24 +2012,24 @@ _deck: direct: "指定使用者" roleTimeline: "角色時間軸" _dialog: - charactersExceeded: "已超過最大字數!現在 {current} / 限制 {max}" - charactersBelow: "低於最少字數!現在 {current} / 限制 {max}" + charactersExceeded: "您的貼文太長了!現時字數 {current}/限制字數 {max}" + charactersBelow: "您的貼文太短了!現時字數 {current}/限制字數 {min}" _disabledTimeline: - title: "停用的時間軸" - description: "目前的角色無法使用這個時間軸。" + title: "時間軸已停用" + description: "目前角色無法使用這個時間軸。" _drivecleaner: - orderBySizeDesc: "檔案由大到小" - orderByCreatedAtAsc: "依照加入的日期順序" + orderBySizeDesc: "按大小降序排列" + orderByCreatedAtAsc: "按新增日期降序排列" _webhookSettings: createWebhook: "建立 Webhook" name: "名稱" - secret: "秘密" - events: "什麼時候運行Webhook" + secret: "密鑰" + events: "何時運行 Webhook" active: "已啟用" _events: follow: "當你追隨時" followed: "當被追隨時" - note: "當發布貼文時" + note: "當發佈貼文時" reply: "當收到回覆時" renote: "當被轉發時" reaction: "當獲得反應時" diff --git a/package.json b/package.json index a62ef7c18..58d153a4f 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "misskey", - "version": "13.14.0-beta.6", + "version": "13.14.0-beta.7", "codename": "nasubi", "repository": { "type": "git", "url": "https://github.com/misskey-dev/misskey.git" }, - "packageManager": "pnpm@8.6.0", + "packageManager": "pnpm@8.6.9", "workspaces": [ "packages/frontend", "packages/backend", diff --git a/packages/backend/check_connect.js b/packages/backend/check_connect.js index ef0a350fb..da78c8ec7 100644 --- a/packages/backend/check_connect.js +++ b/packages/backend/check_connect.js @@ -2,14 +2,7 @@ import Redis from 'ioredis'; import { loadConfig } from './built/config.js'; const config = loadConfig(); -const redis = new Redis({ - port: config.redis.port, - host: config.redis.host, - family: config.redis.family == null ? 0 : config.redis.family, - password: config.redis.pass, - keyPrefix: `${config.redis.prefix}:`, - db: config.redis.db ?? 0, -}); +const redis = new Redis(config.redis); redis.on('connect', () => redis.disconnect()); redis.on('error', (e) => { diff --git a/packages/backend/package.json b/packages/backend/package.json index 564bbf85d..7f64c2a9a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -79,7 +79,6 @@ "ajv": "8.12.0", "archiver": "5.3.1", "async-mutex": "^0.4.0", - "autwh": "0.1.0", "bcryptjs": "2.4.3", "blurhash": "2.0.5", "bullmq": "4.4.0", @@ -93,7 +92,6 @@ "content-disposition": "0.5.4", "date-fns": "2.30.0", "deep-email-validator": "0.1.21", - "escape-regexp": "0.0.1", "fastify": "4.20.0", "feed": "4.2.2", "file-type": "18.5.0", @@ -139,7 +137,6 @@ "rename": "1.0.4", "rss-parser": "3.13.0", "rxjs": "7.8.1", - "s-age": "1.1.2", "sanitize-html": "2.11.0", "semver": "7.5.4", "sharp": "0.32.3", @@ -157,7 +154,6 @@ "typeorm": "0.3.17", "typescript": "5.1.6", "ulid": "2.3.0", - "unzipper": "0.10.14", "vary": "1.1.2", "web-push": "3.6.3", "ws": "8.13.0", @@ -172,7 +168,6 @@ "@types/cbor": "6.0.0", "@types/color-convert": "2.0.0", "@types/content-disposition": "0.5.5", - "@types/escape-regexp": "0.0.1", "@types/fluent-ffmpeg": "2.1.21", "@types/jest": "29.5.3", "@types/js-yaml": "4.0.5", @@ -191,7 +186,6 @@ "@types/qrcode": "1.5.1", "@types/random-seed": "0.3.3", "@types/ratelimiter": "3.4.4", - "@types/redis": "4.0.11", "@types/rename": "1.0.4", "@types/sanitize-html": "2.9.0", "@types/semver": "7.5.0", @@ -199,10 +193,8 @@ "@types/sinonjs__fake-timers": "8.1.2", "@types/tinycolor2": "1.4.3", "@types/tmp": "0.2.3", - "@types/unzipper": "0.10.6", "@types/vary": "1.1.0", "@types/web-push": "3.3.2", - "@types/websocket": "1.0.5", "@types/ws": "8.5.5", "@typescript-eslint/eslint-plugin": "5.61.0", "@typescript-eslint/parser": "5.61.0", diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 406e3192b..4caf4c3e9 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -41,14 +41,7 @@ const $meilisearch: Provider = { const $redis: Provider = { provide: DI.redis, useFactory: (config: Config) => { - return new Redis.Redis({ - port: config.redis.port, - host: config.redis.host, - family: config.redis.family == null ? 0 : config.redis.family, - password: config.redis.pass, - keyPrefix: `${config.redis.prefix}:`, - db: config.redis.db ?? 0, - }); + return new Redis.Redis(config.redis); }, inject: [DI.config], }; @@ -56,14 +49,7 @@ const $redis: Provider = { const $redisForPub: Provider = { provide: DI.redisForPub, useFactory: (config: Config) => { - const redis = new Redis.Redis({ - port: config.redisForPubsub.port, - host: config.redisForPubsub.host, - family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family, - password: config.redisForPubsub.pass, - keyPrefix: `${config.redisForPubsub.prefix}:`, - db: config.redisForPubsub.db ?? 0, - }); + const redis = new Redis.Redis(config.redisForPubsub); return redis; }, inject: [DI.config], @@ -72,14 +58,7 @@ const $redisForPub: Provider = { const $redisForSub: Provider = { provide: DI.redisForSub, useFactory: (config: Config) => { - const redis = new Redis.Redis({ - port: config.redisForPubsub.port, - host: config.redisForPubsub.host, - family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family, - password: config.redisForPubsub.pass, - keyPrefix: `${config.redisForPubsub.prefix}:`, - db: config.redisForPubsub.db ?? 0, - }); + const redis = new Redis.Redis(config.redisForPubsub); redis.subscribe(config.host); return redis; }, diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 8c312256d..253975096 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -6,6 +6,16 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; import * as yaml from 'js-yaml'; +import type { RedisOptions } from 'ioredis'; + +type RedisOptionsSource = Partial & { + host: string; + port: number; + family?: number; + pass: string; + db?: number; + prefix?: string; +}; /** * ユーザーが設定する必要のある情報 @@ -35,30 +45,9 @@ export type Source = { user: string; pass: string; }[]; - redis: { - host: string; - port: number; - family?: number; - pass: string; - db?: number; - prefix?: string; - }; - redisForPubsub?: { - host: string; - port: number; - family?: number; - pass: string; - db?: number; - prefix?: string; - }; - redisForJobQueue?: { - host: string; - port: number; - family?: number; - pass: string; - db?: number; - prefix?: string; - }; + redis: RedisOptionsSource; + redisForPubsub?: RedisOptionsSource; + redisForJobQueue?: RedisOptionsSource; meilisearch?: { host: string; port: string; @@ -119,8 +108,9 @@ export type Mixin = { mediaProxy: string; externalMediaProxyEnabled: boolean; videoThumbnailGenerator: string | null; - redisForPubsub: NonNullable; - redisForJobQueue: NonNullable; + redis: RedisOptions & RedisOptionsSource; + redisForPubsub: RedisOptions & RedisOptionsSource; + redisForJobQueue: RedisOptions & RedisOptionsSource; }; export type Config = Source & Mixin; @@ -182,9 +172,9 @@ export function loadConfig() { config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator : null; - if (!config.redis.prefix) config.redis.prefix = mixin.host; - if (config.redisForPubsub == null) config.redisForPubsub = config.redis; - if (config.redisForJobQueue == null) config.redisForJobQueue = config.redis; + mixin.redis = convertRedisOptions(config.redis, mixin.host); + mixin.redisForPubsub = config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, mixin.host) : mixin.redis; + mixin.redisForJobQueue = config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, mixin.host) : mixin.redis; return Object.assign(config, mixin); } @@ -196,3 +186,14 @@ function tryCreateUrl(url: string) { throw new Error(`url="${url}" is not a valid URL.`); } } + +function convertRedisOptions(options: RedisOptionsSource, host: string): RedisOptions & RedisOptionsSource { + return { + ...options, + password: options.pass, + prefix: options.prefix ?? host, + family: options.family == null ? 0 : options.family, + keyPrefix: `${options.prefix ?? host}:`, + db: options.db ?? 0, + }; +} diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts index 2741cb41e..cef664bf0 100644 --- a/packages/backend/src/core/CreateSystemUserService.ts +++ b/packages/backend/src/core/CreateSystemUserService.ts @@ -33,7 +33,7 @@ export class CreateSystemUserService { // Generate secret const secret = generateNativeUserToken(); - const keyPair = await genRsaKeyPair(4096); + const keyPair = await genRsaKeyPair(); let account!: User; diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index d0d4f802e..546b4cee1 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -108,7 +108,7 @@ export class QueueService { removeOnFail: true, }; - await this.deliverQueue.addBulk(Array.from(inboxes.entries()).map(d => ({ + await this.deliverQueue.addBulk(Array.from(inboxes.entries(), d => ({ name: d[0], data: { user, diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 3b501cf8d..d065b460c 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -220,14 +220,19 @@ export class RoleService implements OnApplicationShutdown { } @bindThis - public async getUserRoles(userId: User['id']) { + public async getUserAssigns(userId: User['id']) { const now = Date.now(); let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); // 期限切れのロールを除外 assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now)); - const assignedRoleIds = assigns.map(x => x.roleId); + return assigns; + } + + @bindThis + public async getUserRoles(userId: User['id']) { const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); - const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id)); + const assigns = await this.getUserAssigns(userId); + const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id)); const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null; const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula)); return [...assignedRoles, ...matchedCondRoles]; diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 1e44406c1..070a9a9e3 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -92,7 +92,7 @@ export class SignupService { const keyPair = await new Promise((res, rej) => generateKeyPair('rsa', { - modulusLength: 4096, + modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem', diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index e89ee4632..8fc083719 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -220,6 +220,23 @@ export class ApPersonService implements OnModuleInit { return null; } + private async resolveAvatarAndBanner(user: RemoteUser, icon: any, image: any): Promise> { + const [avatar, banner] = await Promise.all([icon, image].map(img => { + if (img == null) return null; + if (user == null) throw new Error('failed to create user: user is null'); + return this.apImageService.resolveImage(user, img).catch(() => null); + })); + + return { + avatarId: avatar?.id ?? null, + bannerId: banner?.id ?? null, + avatarUrl: avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null, + bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null, + avatarBlurhash: avatar?.blurhash ?? null, + bannerBlurhash: banner?.blurhash ?? null, + }; + } + /** * Personを作成します。 */ @@ -259,6 +276,16 @@ export class ApPersonService implements OnModuleInit { // Create user let user: RemoteUser | null = null; + + //#region カスタム絵文字取得 + const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host) + .then(_emojis => _emojis.map(emoji => emoji.name)) + .catch(err => { + this.logger.error(`error occured while fetching user emojis`, { stack: err }); + return []; + }); + //#endregion + try { // Start transaction await this.db.transaction(async transactionalEntityManager => { @@ -285,6 +312,7 @@ export class ApPersonService implements OnModuleInit { tags, isBot, isCat: (person as any).isCat === true, + emojis, })) as RemoteUser; await transactionalEntityManager.save(new UserProfile({ @@ -321,6 +349,9 @@ export class ApPersonService implements OnModuleInit { if (user == null) throw new Error('failed to create user: user is null'); + // Register to the cache + this.cacheService.uriPersonCache.set(user.uri, user); + // Register host this.federatedInstanceService.fetch(host).then(async i => { this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); @@ -336,45 +367,16 @@ export class ApPersonService implements OnModuleInit { this.hashtagService.updateUsertags(user, tags); //#region アバターとヘッダー画像をフェッチ - const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => { - if (img == null) return null; - if (user == null) throw new Error('failed to create user: user is null'); - return this.apImageService.resolveImage(user, img).catch(() => null); - })); + try { + const updates = await this.resolveAvatarAndBanner(user, person.icon, person.image); + await this.usersRepository.update(user.id, updates); + user = { ...user, ...updates }; - const avatarId = avatar?.id ?? null; - const bannerId = banner?.id ?? null; - const avatarUrl = avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null; - const bannerUrl = banner ? this.driveFileEntityService.getPublicUrl(banner) : null; - const avatarBlurhash = avatar?.blurhash ?? null; - const bannerBlurhash = banner?.blurhash ?? null; - - await this.usersRepository.update(user.id, { - avatarId, - bannerId, - avatarUrl, - bannerUrl, - avatarBlurhash, - bannerBlurhash, - }); - - user.avatarId = avatarId; - user.bannerId = bannerId; - user.avatarUrl = avatarUrl; - user.bannerUrl = bannerUrl; - user.avatarBlurhash = avatarBlurhash; - user.bannerBlurhash = bannerBlurhash; - //#endregion - - //#region カスタム絵文字取得 - const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => { - this.logger.info(`extractEmojis: ${err}`); - return []; - }); - - const emojiNames = emojis.map(emoji => emoji.name); - - await this.usersRepository.update(user.id, { emojis: emojiNames }); + // Register to the cache + this.cacheService.uriPersonCache.set(user.uri, user); + } catch (err) { + this.logger.error('error occured while fetching user avatar/banner', { stack: err }); + } //#endregion await this.updateFeatured(user.id, resolver).catch(err => this.logger.error(err)); @@ -400,7 +402,7 @@ export class ApPersonService implements OnModuleInit { if (uri.startsWith(`${this.config.url}/`)) return; //#region このサーバーに既に登録されているか - const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser | null; + const exist = await this.fetchPerson(uri) as RemoteUser | null; if (exist === null) return; //#endregion @@ -413,12 +415,6 @@ export class ApPersonService implements OnModuleInit { this.logger.info(`Updating the Person: ${person.id}`); - // アバターとヘッダー画像をフェッチ - const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => { - if (img == null) return null; - return this.apImageService.resolveImage(exist, img).catch(() => null); - })); - // カスタム絵文字取得 const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => { this.logger.info(`extractEmojis: ${e}`); @@ -454,6 +450,7 @@ export class ApPersonService implements OnModuleInit { movedToUri: person.movedTo ?? null, alsoKnownAs: person.alsoKnownAs ?? null, isExplorable: person.discoverable, + ...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))), } as Partial & Pick; const moving = ((): boolean => { @@ -476,18 +473,6 @@ export class ApPersonService implements OnModuleInit { if (moving) updates.movedAt = new Date(); - if (avatar) { - updates.avatarId = avatar.id; - updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar'); - updates.avatarBlurhash = avatar.blurhash; - } - - if (banner) { - updates.bannerId = banner.id; - updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner); - updates.bannerBlurhash = banner.blurhash; - } - // Update user await this.usersRepository.update(exist.id, updates); diff --git a/packages/backend/src/queue/const.ts b/packages/backend/src/queue/const.ts index d240fe70e..d49951a1c 100644 --- a/packages/backend/src/queue/const.ts +++ b/packages/backend/src/queue/const.ts @@ -15,11 +15,8 @@ export const QUEUE = { export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.QueueOptions { return { connection: { - port: config.redisForJobQueue.port, - host: config.redisForJobQueue.host, - family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family, - password: config.redisForJobQueue.pass, - db: config.redisForJobQueue.db ?? 0, + ...config.redisForJobQueue, + keyPrefix: undefined }, prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`, }; diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 4ba749ec5..37b929cb0 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -1,7 +1,7 @@ import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; +import { ZipReader } from 'slacc'; import { DataSource } from 'typeorm'; -import unzipper from 'unzipper'; import { DI } from '@/di-symbols.js'; import type { EmojisRepository, DriveFilesRepository, UsersRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; @@ -72,9 +72,9 @@ export class ImportCustomEmojisProcessorService { } const outputPath = path + '/emojis'; - const unzipStream = fs.createReadStream(destPath); - const extractor = unzipper.Extract({ path: outputPath }); - extractor.on('close', async () => { + try { + this.logger.succ(`Unzipping to ${outputPath}`); + ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath)); const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8'); const meta = JSON.parse(metaRaw); @@ -115,8 +115,12 @@ export class ImportCustomEmojisProcessorService { cleanup(); this.logger.succ('Imported'); - }); - unzipStream.pipe(extractor); - this.logger.succ(`Unzipping to ${outputPath}`); + } catch (e) { + if (e instanceof Error || typeof e === 'string') { + this.logger.error(e); + } + cleanup(); + throw e; + } } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 2fcf0da3f..200ede0b0 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -4,6 +4,7 @@ import type { DriveFilesRepository } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; +import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js'; import { ApiError } from '../../../error.js'; export const meta = { @@ -55,6 +56,7 @@ export default class extends Endpoint { private customEmojiService: CustomEmojiService, + private emojiEntityService: EmojiEntityService, private moderationLogService: ModerationLogService, ) { super(meta, paramDef, async (ps, me) => { @@ -77,9 +79,7 @@ export default class extends Endpoint { emojiId: emoji.id, }); - return { - id: emoji.id, - }; + return this.emojiEntityService.packDetailed(emoji); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index f49d2a096..6f805b6b4 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -61,6 +61,7 @@ export default class extends Endpoint { const signins = await this.signinsRepository.findBy({ userId: user.id }); + const roleAssigns = await this.roleService.getUserAssigns(user.id); const roles = await this.roleService.getUserRoles(user.id); return { @@ -85,6 +86,11 @@ export default class extends Endpoint { signins, policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me), + roleAssigns: roleAssigns.map(a => ({ + createdAt: a.createdAt.toISOString(), + expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null, + roleId: a.roleId, + })), }; }); } diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index e756a9b51..2c4247cb7 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -76,6 +76,11 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchAntenna); } + this.antennasRepository.update(antenna.id, { + isActive: true, + lastUsedAt: new Date(), + }); + const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1 const noteIdsRes = await this.redisClient.xrevrange( `antennaTimeline:${antenna.id}`, @@ -112,11 +117,6 @@ export default class extends Endpoint { this.noteReadService.read(me.id, notes); } - this.antennasRepository.update(antenna.id, { - isActive: true, - lastUsedAt: new Date(), - }); - return await this.noteEntityService.packMany(notes, me); }); } diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 5f980bdbe..55218b644 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -112,6 +112,8 @@ export default class extends Endpoint { withReplies: ps.withReplies, withFile: ps.withFile, notify: ps.notify, + isActive: true, + lastUsedAt: new Date(), }); this.globalEventService.publishInternalEvent('antennaUpdated', await this.antennasRepository.findOneByOrFail({ id: antenna.id })); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index e8985a9cd..a7e39fc02 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -103,7 +103,7 @@ export default class extends Endpoint { const procedures = this.twoFactorAuthenticationService.getProcedures(); if (!(procedures as any)[attestation.fmt]) { - throw new Error('unsupported fmt'); + throw new Error(`unsupported fmt: ${attestation.fmt}. Supported ones: ${Object.keys(procedures)}`); } const verificationData = (procedures as any)[attestation.fmt].verify({ diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug index 98d0c9a78..9bc652b6a 100644 --- a/packages/backend/src/server/web/views/note.pug +++ b/packages/backend/src/server/web/views/note.pug @@ -5,8 +5,8 @@ block vars - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const url = `${config.url}/notes/${note.id}`; - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - - const image = (note.files || []).find(file => file.type.startsWith('image/') && !file.isSensitive) - - const video = (note.files || []).find(file => file.type.startsWith('video/') && !file.isSensitive) + - const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive) + - const videos = (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive) block title = `${title} | ${instanceName}` @@ -19,15 +19,17 @@ block og meta(property='og:title' content= title) meta(property='og:description' content= summary) meta(property='og:url' content= url) - if video - meta(property='og:video:url' content= video.url) - meta(property='og:video:secure_url' content= video.url) - meta(property='og:video:type' content= video.type) - // FIXME: add width and height - // FIXME: add embed player for Twitter - if image + if videos.length + each video in videos + meta(property='og:video:url' content= video.url) + meta(property='og:video:secure_url' content= video.url) + meta(property='og:video:type' content= video.type) + // FIXME: add width and height + // FIXME: add embed player for Twitter + if images.length meta(property='twitter:card' content='summary_large_image') - meta(property='og:image' content= image.url) + each image in images + meta(property='og:image' content= image.url) else meta(property='twitter:card' content='summary') meta(property='og:image' content= avatarUrl) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 394954076..0786c54cd 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,8 +4,9 @@ "scripts": { "watch": "vite", "build": "vite build", - "storybook-dev": "chokidar 'src/**/*.{mdx,ts,vue}' -d 1000 -t 1000 --initial -i '**/*.stories.ts' -c 'pkill -f node_modules/storybook/index.js; node_modules/.bin/tsc -p .storybook && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js && node_modules/.bin/storybook dev -p 6006 --ci'", - "build-storybook": "tsc -p .storybook && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js && storybook build", + "storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"", + "build-storybook-pre": "tsc -p .storybook && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js", + "build-storybook": "pnpm build-storybook-pre && storybook build", "chromatic": "chromatic", "test": "vitest --run", "test-and-coverage": "vitest --run --coverage", @@ -19,7 +20,7 @@ "@rollup/plugin-json": "6.0.0", "@rollup/plugin-replace": "5.0.2", "@rollup/pluginutils": "5.0.2", - "@syuilo/aiscript": "0.14.0", + "@syuilo/aiscript": "0.15.0", "@tabler/icons-webfont": "2.25.0", "@vitejs/plugin-vue": "4.2.3", "@vue-macros/reactivity-transform": "0.3.15", @@ -77,24 +78,24 @@ "vuedraggable": "next" }, "devDependencies": { - "@storybook/addon-actions": "7.1.0", - "@storybook/addon-essentials": "7.1.0", - "@storybook/addon-interactions": "7.1.0", - "@storybook/addon-links": "7.1.0", - "@storybook/addon-storysource": "7.1.0", - "@storybook/addons": "7.1.0", - "@storybook/blocks": "7.1.0", - "@storybook/core-events": "7.1.0", + "@storybook/addon-actions": "7.0.27", + "@storybook/addon-essentials": "7.0.27", + "@storybook/addon-interactions": "7.0.27", + "@storybook/addon-links": "7.0.27", + "@storybook/addon-storysource": "7.0.27", + "@storybook/addons": "7.0.27", + "@storybook/blocks": "7.0.27", + "@storybook/core-events": "7.0.27", "@storybook/jest": "0.1.0", - "@storybook/manager-api": "7.1.0", - "@storybook/preview-api": "7.1.0", - "@storybook/react": "7.1.0", - "@storybook/react-vite": "7.1.0", + "@storybook/manager-api": "7.0.27", + "@storybook/preview-api": "7.0.27", + "@storybook/react": "7.0.27", + "@storybook/react-vite": "7.0.27", "@storybook/testing-library": "0.2.0", - "@storybook/theming": "7.1.0", - "@storybook/types": "7.1.0", - "@storybook/vue3": "7.1.0", - "@storybook/vue3-vite": "7.1.0", + "@storybook/theming": "7.0.27", + "@storybook/types": "7.0.27", + "@storybook/vue3": "7.0.27", + "@storybook/vue3-vite": "7.0.27", "@testing-library/jest-dom": "5.16.5", "@testing-library/vue": "7.0.0", "@types/escape-regexp": "0.0.1", @@ -117,7 +118,6 @@ "@vitest/coverage-v8": "0.33.0", "@vue/runtime-core": "3.3.4", "acorn": "8.10.0", - "chokidar-cli": "3.0.0", "cross-env": "7.0.3", "cypress": "12.17.1", "eslint": "8.45.0", @@ -128,11 +128,12 @@ "micromatch": "4.0.5", "msw": "1.2.2", "msw-storybook-addon": "1.8.0", + "nodemon": "3.0.1", "prettier": "3.0.0", "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.0", - "storybook": "7.1.0", + "storybook": "7.0.27", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "summaly": "github:misskey-dev/summaly", "vite-plugin-turbosnap": "1.0.2", diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 599819b3f..8b3f91731 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -19,7 +19,7 @@
-

NSFW

+

{{ i18n.ts.sensitive }}

diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 9791a88f7..7e5c2c8dc 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -20,7 +20,7 @@ diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index b6ffba6cc..de5195ab4 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -9,7 +9,10 @@ {{ i18n.ts.invitationRequiredToRegister }} -
{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}
+
+
{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}
+
{{ i18n.ts.beSureToReadThisAsItIsImportant }}
+
@@ -19,7 +22,7 @@
  • - {{ i18n.ts.agree }} + {{ i18n.ts.agree }}
    @@ -28,7 +31,7 @@ {{ i18n.ts.termsOfService }} - {{ i18n.ts.agree }} + {{ i18n.ts.agree }} @@ -37,7 +40,7 @@ {{ i18n.ts.basicNotesBeforeCreateAccount }} - {{ i18n.ts.agree }} + {{ i18n.ts.agree }}
    {{ i18n.ts.pleaseAgreeAllToContinue }}
    @@ -52,13 +55,14 @@