Merge branch 'develop' into pizzax-indexeddb

This commit is contained in:
tamaina 2022-03-27 22:42:05 +09:00
commit 7314643b8d
484 changed files with 5750 additions and 4207 deletions

View file

@ -15,10 +15,7 @@ url: https://example.tld/
#───┘ Port and TLS settings └───────────────────────────────────
#
# Misskey supports two deployment options for public.
#
# Option 1: With Reverse Proxy
# Misskey requires a reverse proxy to support HTTPS connections.
#
# +----- https://example.tld/ ------------+
# +------+ |+-------------+ +----------------+|
@ -26,30 +23,12 @@ url: https://example.tld/
# +------+ |+-------------+ +----------------+|
# +---------------------------------------+
#
# You need to setup reverse proxy. (eg. nginx)
# You do not define 'https' section.
# You need to set up a reverse proxy. (e.g. nginx)
# An encrypted connection with HTTPS is highly recommended
# because tokens may be transferred in GET requests.
# Option 2: Standalone
#
# +- https://example.tld/ -+
# +------+ | +---------------+ |
# | User | ---> | | Misskey (443) | |
# +------+ | +---------------+ |
# +------------------------+
#
# You need to run Misskey as root.
# You need to set Certificate in 'https' section.
# To use option 1, uncomment below line.
#port: 3000 # A port that your Misskey server should listen.
# To use option 2, uncomment below lines.
#port: 443
#https:
# # path for certification
# key: /etc/letsencrypt/live/example.tld/privkey.pem
# cert: /etc/letsencrypt/live/example.tld/fullchain.pem
# The port that your Misskey server should listen on.
port: 3000
# ┌──────────────────────────┐
#───┘ PostgreSQL configuration └────────────────────────────────

View file

@ -12,21 +12,52 @@ You should also include the user name that made the change.
## 12.x.x (unreleased)
### Improvements
- Bull Dashboardを組み込み、ジョブキューの確認や操作を行えるように @syuilo
- Check that installed Node.js version fulfills version requirement @ThatOneCalculator
- Server: performance improvements @syuilo
### Bugfixes
- API: fix endpoint endpoint @Johann150
## 12.108.1 (2022/03/12)
### Bugfixes
- リレーが動作しない問題を修正 @xianonn
- ulidを使用していると動作しない問題を修正 @syuilo
- 外部からOGPが正しく取得できない問題を修正 @syuilo
- instance can not get the files from other instance when there are items in allowedPrivateNetworks in .config/default.yml @ybw2016v
## 12.108.0 (2022/03/09)
### NOTE
このバージョンからNode v16.14.0以降が必要です
### Changes
- ートの最大文字数を設定できる機能が廃止され、デフォルトで一律3000文字になりました @syuilo
- Misskey can no longer terminate HTTPS connections. @Johann150
- If you did not use a reverse proxy (e.g. nginx) before, you will probably need to adjust
your configuration file and set up a reverse proxy. The `https` configuration key is no
longer recognized!
### Improvements
- インスタンスデフォルトテーマを設定できるように @syuilo
- ミュートに期限を設定できるように @syuilo
- アンケートが終了したときに通知が作成されるように @syuilo
- プロフィールの追加情報を最大16まで保存できるように @syuilo
- 連合チャートにPub&Subを追加 @syuilo
- 連合チャートにActiveを追加 @syuilo
- デフォルトで10秒以上時間がかかるデータベースへのクエリは中断されるように @syuilo
- 設定ファイルの`db.extra`に`statement_timeout`を設定することでタイムアウト時間を変更できます
- Client: スプラッシュスクリーンにインスタンスのアイコンを表示するように @syuilo
### Bugfixes
- Client: リアクションピッカーの高さが低くなったまま戻らないことがあるのを修正 @syuilo
- Client: ユーザー名オートコンプリートが正しく動作しない問題を修正 @syuilo
- Client: タッチ操作だとウィジェットの編集がしにくいのを修正 @xianonn
- Client: register_note_view_interruptor()が動かないのを修正 @syuilo
- Client: iPhone X以降(?)でページの内容が全て表示しきれないのを修正 @tamaina
- Client: fix image caption on mobile @nullobsi
## 12.107.0 (2022/02/12)

View file

@ -6,6 +6,9 @@ Also, you might receive comments on your Issue/PR in Japanese, but you do not ne
The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
It will also allow the reader to use the translation tool of their preference if necessary.
## Roadmap
See [ROADMAP.md](./ROADMAP.md)
## Issues
Before creating an issue, please check the following:
- To avoid duplication, please search for similar issues before creating a new issue.
@ -198,11 +201,13 @@ MongoDBの時とは違い、findOneでレコードを取得する時に対象レ
MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください
### Migration作成方法
```
npx ts-node ./node_modules/typeorm/cli.js migration:generate -n 変更の名前 -o
packages/backendで:
```sh
npx typeorm migration:generate -d ormconfig.js -o <migration name>
```
作成されたスクリプトは不必要な変更を含むため除去してください。
- 生成後、ファイルをmigration下に移してください
- 作成されたスクリプトは不必要な変更を含むため除去してください
### コネクションには`markRaw`せよ
**Vueのコンポーネントのdataオプションとして**misskey.jsのコネクションを設定するとき、必ず`markRaw`でラップしてください。インスタンスが不必要にリアクティブ化されることで、misskey.js内の処理で不具合が発生するとともに、パフォーマンス上の問題にも繋がる。なお、Composition APIを使う場合はこの限りではない(リアクティブ化はマニュアルなため)。

View file

@ -48,7 +48,7 @@
## Sponsors
<div align="center">
<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank" style="display: inline-block;"><img src="https://rss3.io/assets/images/Logo.svg" alt="RSS3" style="display: inline-block; height: 60px;"></a>
<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a>
</div>
## Backers

28
ROADMAP.md Normal file
View file

@ -0,0 +1,28 @@
# Roadmap
The order of individual tasks is a guide only and is subject to change depending on the situation.
Also, the later tasks are more indefinite and are subject to change as development progresses.
## (1) Improve maintainability \<current phase\>
This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development.
- Make the number of type errors zero (backend)
- Probably need to switch some libraries to others that make it difficult to reduce type errors
- e.g. koa to fastify https://github.com/misskey-dev/misskey/issues/7537
- Improve CI
- Fix tests
- mocha, jest, etc. do not support the combination of `TypeScript + ESM + Path alias`, and the tests currently do not work.
- Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986
- Add more tests
- May need to implement a mechanism that allows for DI
- Improve documentation
## (2) Improve functionality
Once Phase 1 is complete and an environment conducive to the development of a stable system is in place, the implementation of new functions can begin gradually.
- OAuth2 support https://github.com/misskey-dev/misskey/issues/8262
- GraphQL support?
## (3) Improve scalability
Once the development of the feature has settled down, this may be an opportunity to make larger modifications.
- Rewriting in Rust?

View file

@ -32,7 +32,7 @@ uploading: "يرفع..."
save: "حفظ"
users: "المستخدمون"
addUser: "اضافة مستخدم"
favorite: "إضافة إلى المفضلة"
favorite: "أضفها للمفضلة"
favorites: "المفضلات"
unfavorite: "إزالة من المفضلة"
favorited: "أُضيف إلى المفضلة."
@ -106,6 +106,7 @@ clickToShow: "اضغط للعرض"
sensitive: "محتوى حساس"
add: "إضافة"
reaction: "التفاعلات"
reactionSetting: "التفاعلات المراد عرضها في منتقي التفاعلات."
reactionSettingDescription2: "اسحب لترتيب ، انقر للحذف ، استخدم \"+\" للإضافة."
rememberNoteVisibility: "تذكر إعدادت مدى رؤية الملاحظات"
attachCancel: "أزل المرفق"
@ -139,6 +140,8 @@ flagAsBot: "علّمه كحساب آلي"
flagAsBotDescription: "فعّل هذا الخيار إذا كان هذا الحساب يُدار عبر برمجية. إذا فُعل فسيكون بمثابة علامة للمطورين الآخرين لتجنب سلاسل لا متناهية من التفاعل بين حسابات الآلية وضبط أنظمة ميسكي للتعامل مع هذا الحساب كآلي."
flagAsCat: "علّم هذا الحساب كحساب قط"
flagAsCatDescription: "فعّل هذا الخيار لوضع علامة على الحساب لتوضيح أنه حساب قط."
flagShowTimelineReplies: "أظهر التعليقات في الخيط الزمني"
flagShowTimelineRepliesDescription: "يظهر الردود في الخط الزمني"
autoAcceptFollowed: "اقبل طلبات المتابعة تلقائيا من الحسابات المتابَعة"
addAccount: "أضف حساباً"
loginFailed: "فشل الولوج"
@ -230,6 +233,8 @@ resetAreYouSure: "هل تريد إعادة التعيين؟"
saved: "حُفظ"
messaging: "المحادثة"
upload: "ارفع"
keepOriginalUploading: "ابق الصورة الأصلية"
keepOriginalUploadingDescription: "يحفظ الصور المرفوعة على حالتها الأصلية، وان عطّل ستولد نسخة مخصصة من الصورة."
fromDrive: "من المخزن"
fromUrl: "عبر رابط"
uploadFromUrl: "ارفع عبر رابط"
@ -276,6 +281,7 @@ emptyDrive: "قرص التخزين فارغ"
emptyFolder: "هذا المجلد فارغ"
unableToDelete: "لا يمكن حذفه"
inputNewFileName: "ادخل الإسم الجديد للملف"
inputNewDescription: "أدخل تعليقًا توضيحيًا"
inputNewFolderName: "ادخل الإسم الجديد للمجلد"
circularReferenceFolder: "المجلد المستهدف ينتمي للمجلد الذي تريد حذفه"
hasChildFilesOrFolders: "الان الملف غير فارغ. لا يمكن حذفه"
@ -315,8 +321,6 @@ disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من
registration: "إنشاء حساب"
enableRegistration: "تفعيل إنشاء الحسابات الجديدة"
invite: "دعوة"
proxyRemoteFiles: "جلب الملفات البعيدة عبر وكيل"
proxyRemoteFilesDescription: "إذا فُعّل هذا الإعداد ، ستُجلب الملفات البعيدة غير الموجودة في التخزين المحلي للخادم عبر وكيل وتُنشأ لها صور مصغرة. لن يأثر على تخزين الخادم."
driveCapacityPerLocalAccount: "حصة التخزين لكل مستخدم محلي"
driveCapacityPerRemoteAccount: "حصة التخزين لكل مستخدم بعيد"
inMb: "بالميغابايت"
@ -345,6 +349,7 @@ name: "الإسم"
antennaSource: "مصدر الهوائي"
antennaKeywords: "الكلمات المفتاحية للإستقبال"
antennaExcludeKeywords: "الكلمات المفتاحية المستثناة"
antennaKeywordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\""
notifyAntenna: "نبهني بصول ملاحظات جديدة"
withFileAntenna: "ملاحظات تحوي ملفات فقط"
antennaUsersDescription: "اكتب اسم مستخدم لكل سطر"
@ -410,7 +415,6 @@ next: "التالية"
retype: "أعد الكتابة"
noteOf: "ملاحظات {user}"
inviteToGroup: "دعوة إلى فريق"
maxNoteTextLength: "حد عدد المحارف لكل ملاحظة"
quoteAttached: "اِقتُبسَ"
quoteQuestion: "أتريد تضمينها كاقتباس"
noMessagesYet: "ليس هناك رسائل بعد"
@ -469,6 +473,7 @@ hideThisNote: "إخفاء هذه الملاحظة"
showFeaturedNotesInTimeline: "أظهر الملاحظات الشائعة في الخيط الزمني"
objectStorageBaseUrl: "الرابط الأساسي"
objectStoragePrefix: "البادئة"
objectStoragePrefixDesc: "ستُحفظ الملفات في مجلدات تحوي اسماءها هذه البادئة."
objectStorageEndpoint: "نقطة النهاية"
objectStorageRegion: "المنطقة"
objectStorageUseSSL: "استخدم SSL"
@ -630,7 +635,10 @@ experimentalFeatures: "ميّزات اختبارية"
developer: "المطور"
makeExplorable: "أظهر الحساب في صفحة \"استكشاف\""
makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\""
wide: "عريض"
narrow: "رفيع"
reloadToApplySetting: "سيُطبق هذا الإعداد بعد إعادة تحميل الصفحة، أتريد إعادة تحميلها الآن؟"
needReloadToApply: "سيطبق هذا بعد إعادة التحميل."
showTitlebar: "اعرض شريط العنوان"
clearCache: "امسح التخزين المؤقت"
onlineUsersCount: "{n} مستخدم متصل"
@ -661,6 +669,7 @@ capacity: "السعة"
inUse: "مستخدم"
editCode: "حرر الشفرة"
apply: "تطبيق"
receiveAnnouncementFromInstance: "استلم إشعارات من هذا المثيل"
emailNotification: "إشعارات البريد الكتروني"
inChannelSearch: "ابحث عن قناة"
useReactionPickerForContextMenu: "افتح منتقي التفاعلات عند النقر بالزر الأيمن"
@ -674,6 +683,7 @@ unlikeConfirm: "أتريد إلغاء إعجابك؟"
fullView: "ملء الشاشة"
quitFullView: "اخرج من وضع ملء للشاشة"
addDescription: "أضف وصفًا"
userPagePinTip: "لعرض ملاحظة هنا اختر \"دبسها على الصفحة الشخصية\" من قائمة تلك الملاحظة."
notSpecifiedMentionWarning: "في الملاحظة ذكر لمستخدمين لن يستلموها."
info: "عن"
userInfo: "معلومات المستخدم"
@ -748,11 +758,30 @@ makeReactionsPublicDescription: "هذا سيجعل قائمة تفاعلاتك
classic: "تقليدي"
muteThread: "اكتم النقاش"
unmuteThread: "ارفع الكتم عن النقاش"
ffVisibility: "مرئية المتابِعين/المتابَعين"
ffVisibilityDescription: "يسمح لك بتحديد من يمكنهم رؤية متابِعيك ومتابَعيك."
deleteAccountConfirm: "سيحذف حسابك نهائيًا، أتريد المتابعة؟"
incorrectPassword: "كلمة السر خاطئة."
voteConfirm: "متيقِّن من تصويتك لـ {choice}؟"
hide: "إخفاء"
leaveGroup: "مغادرة الفريق"
leaveGroupConfirm: "متيقن من مغادرة \"{name}\"؟"
welcomeBackWithName: "مرحبًا بك مجددًا {name}"
clickToFinishEmailVerification: "انقر [{ok}] لاستيثاق بريدك الإلكتروني."
overridedDeviceKind: "نوع الجهاز"
smartphone: "هاتف ذكي"
tablet: "جهاز لوحي"
auto: "تلقائي"
themeColor: "لون السمة"
size: "الحجم"
numberOfColumn: "عدد الأعمدة"
searchByGoogle: "غوغل"
mutePeriod: "مدة الكتم"
indefinitely: "أبدًا"
tenMinutes: "10 دقائق"
oneHour: "ساعة"
oneDay: "يوم"
oneWeek: "أسبوع"
_emailUnavailable:
used: "هذا البريد الإلكتروني مستخدم"
format: "صيغة البريد الإلكتروني غير صالحة"
@ -775,6 +804,7 @@ _accountDelete:
inProgress: "عملية الحذف جارية"
_ad:
back: "رجوع"
reduceFrequencyOfThisAd: "قلل عرض هذا الإعلان"
_forgotPassword:
enterEmail: "أدخل البريد الإلكتروني المرتبط بحسابك لكي يرسل إليك رابط لإعادة تعيين كلمة المرور."
ifNoEmail: "إذا لم تربط حسابك ببريد إلكتروني سيتوجب عليك التواصل مع مدير الموقع."
@ -801,7 +831,7 @@ _registry:
createKey: "أنشئ مفتاحًا"
_aboutMisskey:
about: "ميسكي هو برمجية مفتوحة المصدر يطورها syuilo منذ 2014."
contributors: "المساهم الرئيسي"
contributors: "المساهمون الرئيسيون"
allContributors: "كل المساهمين"
source: "الشفرة المصدرية"
translation: "ترجم ميسكي"
@ -823,8 +853,11 @@ _mfm:
urlDescription: "يمكن عرض الروابط"
link: "رابط"
bold: "عريض"
boldDescription: "جعل الحروف أثخن لإبرازها."
small: "صغير"
smallDescription: "يعرض المحتوى صغيرًا ورفيعًا."
center: "وسط"
centerDescription: "يمركز المحتوى في الوَسَط."
quote: "اقتبس"
emoji: "إيموجي مخصص"
search: "البحث"
@ -1088,9 +1121,12 @@ _postForm:
quotePlaceholder: "اقتبس هذه الملاحظة…"
channelPlaceholder: "انشر في قناة..."
_placeholders:
a: "ما الذي تنوي فعله؟"
b: "ماذا يحدث حولك ؟"
c: "ما الذي تفكر فيه؟"
d: "ما الذي تريد قوله؟"
e: "أكتب..."
f: "بانتظارك لتكتب..."
_profile:
name: "الإسم"
username: "اسم المستخدم"
@ -1109,23 +1145,25 @@ _exportOrImport:
muteList: "المستخدمون المكتومون"
blockingList: "المستخدمون المحجوبون"
userLists: "القوائم"
excludeMutingUsers: "استثن الحسابات المكتومة"
excludeInactiveUsers: "استثن المستخدمين الخاملين"
_charts:
federation: "الفديرالية"
apRequest: "الطلبات"
usersIncDec: "اختلاف عدد المستخدمين"
usersIncDec: "تباين عدد المستخدمين"
usersTotal: "مجموع عدد المستخدمين والمستخدمات"
activeUsers: "المستخدمون النشطون"
notesIncDec: "اختلاف عدد الملاحظات"
localNotesIncDec: "اختلاف عدد الملاحظات المحلية"
remoteNotesIncDec: "اختلاف عدد الملاحظات البعيدة"
notesIncDec: "تباين عدد الملاحظات"
localNotesIncDec: "تباين عدد الملاحظات المحلية"
remoteNotesIncDec: "تباين عدد الملاحظات البعيدة"
notesTotal: "إجمالي الملاحظات"
filesIncDec: "اختلاف عدد الملفات"
filesIncDec: "تباين عدد الملفات"
filesTotal: "العدد الإجمالي للملفات"
_instanceCharts:
requests: "الطلبات"
users: "اختلاف عدد المستخدمين"
notes: "اختلاف عدد الملاحظات"
files: "اختلاف عدد الملفات"
users: "تباين عدد المستخدمين"
notes: "تباين عدد الملاحظات"
files: "تباين عدد الملفات"
_timelines:
home: "الرئيسي"
local: "المحلي"
@ -1139,14 +1177,21 @@ _pages:
updated: "نجح تعديل الصفحة"
deleted: "نجح حذف الصفحة"
pageSetting: "إعدادات الصفحة"
nameAlreadyExists: "رابط الصفحة موجود مسبقًا"
invalidNameTitle: "رابط الصفحة ليس صالحًا"
invalidNameText: "تأكد أن عنوان الصفحة ليس فارغًا"
editThisPage: "عدّل هذه الصفحة"
viewSource: "اظهر المصدر"
viewPage: "اعرض صفحاتك"
like: "أعجبني"
unlike: "أزل الإعجاب"
my: "صفحاتي"
liked: "الصفحات المُعجب بها"
featured: "الأكثر شعبية"
contents: "المحتوى"
variables: "متغيّرات"
title: "العنوان"
url: "رابط الصفحة"
summary: "ملخص الصفحة"
alignCenter: "توسيط العناصر"
hideTitleWhenPinned: "اخف عنوان الصفحة عند تدبيسها في ملف الشخصي"
@ -1401,6 +1446,7 @@ _notification:
youReceivedFollowRequest: "تلقيتَ طلب متابعة"
yourFollowRequestAccepted: "قُبل طلب المتابعة"
youWereInvitedToGroup: "دُعيت إلى فريقٍ"
pollEnded: "ظهرت نتائج الاستطلاع"
_types:
all: "الكل"
follow: "متابِعون جدد"
@ -1409,6 +1455,7 @@ _notification:
renote: "أعد النشر"
quote: "الاقتباسات"
reaction: "التفاعلات"
pollVote: "مصوِت شارك في الاستطلاع"
receiveFollowRequest: "طلبات المتابعة المتلقاة"
followRequestAccepted: "طلبات المتابعة المقبولة"
app: "إشعارات التطبيقات المرتبطة"

View file

@ -325,8 +325,6 @@ disablingTimelinesInfo: "আপনি এই টাইমলাইনগুল
registration: "নিবন্ধন"
enableRegistration: "নতুন ব্যাবহারকারী নিবন্ধন চালু করুন"
invite: "আমন্ত্রণ"
proxyRemoteFiles: "রিমোট ফাইলসমুহ প্রক্সি করুন"
proxyRemoteFilesDescription: "যখন এই সেটিংটি চালু থাকে, তখন অসংরক্ষিত বা অতিরিক্ত ক্ষমতার কারণে দূরবর্তী ফাইলগুলিকে স্থানীয়ভাবে প্রক্সি করা হবে এবং থাম্বনেলগুলিও তৈরি করা হবে৷ সার্ভার স্টোরেজ ব্যাবহার করে না,"
driveCapacityPerLocalAccount: "প্রত্যেক স্থানীয় ব্যাবহারকারীর জন্য ড্রাইভের জায়গা"
driveCapacityPerRemoteAccount: "প্রত্যেক রিমোট ব্যাবহারকারীর জন্য ড্রাইভের জায়গা"
inMb: "মেগাবাইটে লিখুন"
@ -422,7 +420,6 @@ next: "পরবর্তী"
retype: "পুনঃ প্রবেশ"
noteOf: "{user} এর নোট"
inviteToGroup: "গ্রুপে আমন্ত্রণ জানান"
maxNoteTextLength: "নোট এর সর্বোচ্চ দৈর্ঘ্য"
quoteAttached: "উদ্ধৃত"
quoteQuestion: "উদ্ধৃতি হিসাবে সংযুক্ত করবেন?"
noMessagesYet: "কোন মেসেজ নেই"
@ -831,6 +828,10 @@ smartphone: "স্মার্টফোন"
tablet: "ট্যাবলেট"
auto: "স্বয়ংক্রিয়"
themeColor: "থিমের রং"
size: "আকার"
numberOfColumn: "কলামের সংখ্যা"
searchByGoogle: "গুগল"
indefinitely: "অনির্দিষ্ট"
_emailUnavailable:
used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"

26
locales/ca-ES.yml Normal file
View file

@ -0,0 +1,26 @@
---
_lang_: "Català"
headlineMisskey: "Una xarxa connectada per notes"
search: "Cercar"
notifications: "Notificacions"
username: "Nom d'usuari"
password: "Contrasenya"
forgotPassword: "Contrasenya oblidada"
fetchingAsApObject: "Cercant en el Fediverse..."
ok: "OK"
gotIt: "Ho he entès!"
cancel: "Cancel·lar"
smtpUser: "Nom d'usuari"
smtpPass: "Contrasenya"
searchByGoogle: "Cercar"
_mfm:
search: "Cercar"
_sfx:
notification: "Notificacions"
_widgets:
notifications: "Notificacions"
_profile:
username: "Nom d'usuari"
_deck:
_columns:
notifications: "Notificacions"

View file

@ -449,6 +449,7 @@ clearCache: "Vyprázdnit mezipaměť"
info: "Informace"
user: "Uživatelé"
administration: "Administrace"
searchByGoogle: "Vyhledávání"
_email:
_follow:
title: "Máte nového následovníka"

View file

@ -14,16 +14,16 @@ gotIt: "Verstanden!"
cancel: "Abbrechen"
enterUsername: "Benutzername eingeben"
renotedBy: "Renote von {user}"
noNotes: "Keine Notizen"
noNotifications: "Keine Benachrichtigungen"
noNotes: "Keine Notizen gefunden"
noNotifications: "Keine Benachrichtigungen gefunden"
instance: "Instanz"
settings: "Einstellungen"
basicSettings: "Allgemeine Einstellungen"
otherSettings: "Weitere Einstellungen"
openInWindow: "In Fenster öffnen"
openInWindow: "In einem Fenster öffnen"
profile: "Profil"
timeline: "Chronik"
noAccountDescription: "Dieser Nutzer hat seine Profilbeschreibung noch nicht ausgefüllt."
noAccountDescription: "Dieser Nutzer hat seine Profilbeschreibung noch nicht ausgefüllt"
login: "Anmelden"
loggingIn: "Du wirst angemeldet …"
logout: "Abmelden"
@ -38,8 +38,8 @@ unfavorite: "Aus Favoriten entfernen"
favorited: "Zu Favoriten hinzugefügt."
alreadyFavorited: "Bereits zu den Favoriten hinzugefügt."
cantFavorite: "Hinzufügen zu Favoriten fehlgeschlagen."
pin: "Anheften"
unpin: "Lösen"
pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
delete: "Löschen"
@ -47,7 +47,7 @@ deleteAndEdit: "Löschen und Bearbeiten"
deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen."
addToList: "Zu Liste hinzufügen"
sendMessage: "Nachricht senden"
copyUsername: "Benutzername kopieren"
copyUsername: "Benutzernamen kopieren"
searchUser: "Nach einem Benutzer suchen"
reply: "Antworten"
loadMore: "Mehr laden"
@ -63,12 +63,12 @@ import: "Import"
export: "Export"
files: "Dateien"
download: "Herunterladen"
driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Die zugehörige Notiz wird ebenso verschwinden."
driveFileDeleteConfirm: "Möchtest du die Datei „{name}“ wirklich löschen? Notizen mit dieser Datei werden ebenso verschwinden."
unfollowConfirm: "Möchtest du {name} nicht mehr folgen?"
exportRequested: "Du hast einen Export angefragt. Dies kann etwas Zeit in Anspruch nehmen. Sobald der Export abgeschlossen ist, wird er deiner Drive hinzugefügt."
importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen."
lists: "Listen"
noLists: "Keine Listen"
noLists: "Keine Listen gefunden"
note: "Notiz"
notes: "Notizen"
following: "Folgt"
@ -79,16 +79,16 @@ manageLists: "Listen verwalten"
error: "Fehler"
somethingHappened: "Ein Fehler ist aufgetreten"
retry: "Wiederholen"
pageLoadError: "Laden der Seite fehlgeschlagen."
pageLoadError: "Die Seite konnte nicht geladen werden."
pageLoadErrorDescription: "Dieser Fehler wird meist durch Netzwerkfehler oder den Browser-Cache verursacht. Bitte leere den Cache oder versuche es nach einiger Zeit erneut."
serverIsDead: "Dieser Server antwortet nicht. Bitte warte einen Moment und versuche es dann erneut."
youShouldUpgradeClient: "Bitte aktualisiere diese Seite, um eine neuere Version deines Clients zu verwenden."
enterListName: "Name der Liste eingeben"
enterListName: "Listennamen eingeben"
privacy: "Privatsphäre"
makeFollowManuallyApprove: "Follow-Anfragen benötigen Bestätigung"
defaultNoteVisibility: "Standardsichtbarkeit"
follow: "Folgen"
followRequest: "Follow-Anfrage"
followRequest: "Follow-Anfrage senden"
followRequests: "Follow-Anfragen"
unfollow: "Nicht mehr folgen"
followRequestPending: "Follow-Anfrage ausstehend"
@ -107,11 +107,11 @@ sensitive: "NSFW"
add: "Hinzufügen"
reaction: "Reaktionen"
reactionSetting: "In der Reaktionsauswahl anzuzeigende Reaktionen"
reactionSettingDescription2: "Ziehe zum Anordnen, klicke zum Löschen, drücke + zum Hinzufügen"
reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen"
rememberNoteVisibility: "Notizsichtbarkeit merken"
attachCancel: "Anhang entfernen"
markAsSensitive: "Als NSFW markieren"
unmarkAsSensitive: "NSFW-Markierung entfernen"
unmarkAsSensitive: "Als nicht NSFW markieren"
enterFileName: "Dateinamen eingeben"
mute: "Stummschalten"
unmute: "Stummschaltung aufheben"
@ -129,20 +129,20 @@ selectWidget: "Widget auswählen"
editWidgets: "Widgets bearbeiten"
editWidgetsExit: "Fertig"
customEmojis: "Benutzerdefinierte Emojis"
emoji: "Emojis"
emoji: "Emoji"
emojis: "Emojis"
emojiName: "Emoji-Name"
emojiUrl: "Emoji-URL"
addEmoji: "Emoji hinzufügen"
settingGuide: "Empfohlene Einstellung"
cacheRemoteFiles: "Dateien von fremden Instanzen im Cache speichern"
cacheRemoteFilesDescription: "Ist diese Einstellung deaktiviert, so werden Dateien fremder Instanzen direkt von dort geladen. Hierdurch wird Speicherplatz auf dem Server gespart, aber durch fehlende Generierung von Vorschaubildern mehr Bandbreite verwendet."
cacheRemoteFilesDescription: "Ist diese Einstellung deaktiviert, so werden Dateien fremder Instanzen direkt von dort geladen. Hierdurch wird Speicherplatz auf diesem Server gespart, aber durch fehlende Generierung von Vorschaubildern mehr Bandbreite verwendet."
flagAsBot: "Als Bot markieren"
flagAsBotDescription: "Aktiviere diese Option, falls dieses Benutzerkonto durch ein Programm gesteuert wird. Falls aktiviert, agiert es als Flag für andere Entwickler zur Verhinderung von endlosen Kettenreaktionen mit anderen Bots und lässt Misskeys interne Systeme dieses Benutzerkonto als Bot behandeln."
flagAsCat: "Als Katze markieren"
flagAsCatDescription: "Aktiviere diese Option, um dieses Benutzerkonto als Katze zu markieren."
flagShowTimelineReplies: "Antworten in der Chronik anzeigen"
flagShowTimelineRepliesDescription: "Ist diese Option aktiviert, so werden Antworten von Benutzern auf die Notizen anderer Benuzter in der Chronik an angezeigt."
flagShowTimelineRepliesDescription: "Ist diese Option aktiviert, so werden Antworten von Benutzern auf die Notizen anderer Benutzer in der Chronik angezeigt."
autoAcceptFollowed: "Follow-Anfragen von Benutzern, denen du folgst, automatisch akzeptieren"
addAccount: "Benutzerkonto hinzufügen"
loginFailed: "Anmeldung fehlgeschlagen"
@ -151,12 +151,12 @@ general: "Allgemein"
wallpaper: "Hintergrund"
setWallpaper: "Hintergrund festlegen"
removeWallpaper: "Hintergrund entfernen"
searchWith: "Suche: {q}"
searchWith: "Suchen: {q}"
youHaveNoLists: "Du hast keine Listen"
followConfirm: "Möchtest du {name} wirklich folgen?"
proxyAccount: "Proxy-Benutzerkonto"
proxyAccountDescription: "Ein Proxy-Benutzerkonto ist ein Benutzerkonto, das sich für Nutzer unter bestimmten Konditionen wie ein Follower aus einer fremden Instanz verhält. Zum Beispiel wird die Aktivität eines Nutzers aus einer fremden Instanz nicht an diese Instanz übermittelt, falls es keinen Benutzer dieser Instanz gibt, der diesem Nutzer aus fremder Instanz folgt. In diesem Fall folgt stattdessen das Proxy-Benutzerkonto."
host: "Host"
host: "Hostname"
selectUser: "Benutzer auswählen"
recipient: "Empfänger"
annotation: "Anmerkung"
@ -194,17 +194,17 @@ blockedInstancesDescription: "Gib die Hostnamen der Instanzen, welche blockiert
muteAndBlock: "Stummschaltungen und Blockierungen"
mutedUsers: "Stummgeschaltete Benutzer"
blockedUsers: "Blockierte Benutzer"
noUsers: "Keine Benutzer"
noUsers: "Keine Benutzer gefunden"
editProfile: "Profil bearbeiten"
noteDeleteConfirm: "Möchtest du diese Notiz wirklich löschen?"
pinLimitExceeded: "Es können nicht noch mehr Notizen angeheftet werden"
intro: "Misskey Installation abgeschlossen! Lass uns nun ein Administratorkonto erstellen."
pinLimitExceeded: "Du kannst nicht noch mehr Notizen anheften."
intro: "Misskey ist installiert! Lass uns nun ein Administratorkonto einrichten."
done: "Fertig"
processing: "In Bearbeitung …"
preview: "Vorschau"
default: "Standard"
noCustomEmojis: "Keine benutzerdefinierten Emojis vorhanden"
noJobs: "Es gibt keine Jobs"
noCustomEmojis: "Keine benutzerdefinierten Emojis gefunden"
noJobs: "Keine Jobs vorhanden"
federating: "Wird föderiert"
blocked: "Blockiert"
suspended: "Gesperrt"
@ -217,10 +217,10 @@ instanceFollowers: "Follower der Instanz"
instanceUsers: "Benutzer der Instanz"
changePassword: "Passwort ändern"
security: "Sicherheit"
retypedNotMatch: "Beide Eingaben stimmen nicht überein."
retypedNotMatch: "Die Eingaben stimmen nicht überein."
currentPassword: "Aktuelles Passwort"
newPassword: "Neues Passwort"
newPasswordRetype: "Neues Passwort (wiederholen)"
newPasswordRetype: "Neues Passwort bestätigen"
attachFile: "Datei anhängen"
more: "Mehr!"
featured: "Beliebt"
@ -234,7 +234,7 @@ removed: "Erfolgreich gelöscht"
removeAreYouSure: "Möchtest du „{x}“ wirklich entfernen?"
deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?"
resetAreYouSure: "Wirklich zurücksetzen?"
saved: "Gespeichert"
saved: "Erfolgreich gespeichert"
messaging: "Chat"
upload: "Hochladen"
keepOriginalUploading: "Originalbild speichern"
@ -295,7 +295,7 @@ avatar: "Profilbild"
banner: "Banner"
nsfw: "NSFW"
whenServerDisconnected: "Bei Verbindungsverlust zum Server"
disconnectedFromServer: "Verbindung zum Server wurde getrennt"
disconnectedFromServer: "Die Verbindung zum Server wurde getrennt"
reload: "Aktualisieren"
doNothing: "Ignorieren"
reloadConfirm: "Seite neu laden?"
@ -307,7 +307,7 @@ normal: "Normal"
instanceName: "Name der Instanz"
instanceDescription: "Beschreibung der Instanz"
maintainerName: "Betreiber"
maintainerEmail: "Betreiber-E-Mail"
maintainerEmail: "Betreiber-Email"
tosUrl: "URL der Nutzungsbedingungen"
thisYear: "Jahr"
thisMonth: "Monat"
@ -325,20 +325,18 @@ disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf
registration: "Registrieren"
enableRegistration: "Registration neuer Benutzer erlauben"
invite: "Einladen"
proxyRemoteFiles: "Dateien fremder Instanzen durch Proxy leiten"
proxyRemoteFilesDescription: "Wenn diese Einstellung aktiviert ist, dann werden Dateien von fremdem Instanzen, welche entweder nicht lokal gespeichert sind oder durch Überschreiten des Speicherlimits gelöscht wurden, durch einen Proxy geleitet. Hierbei wird auch ein Vorschaubild generiert. \n Dies hat keinen Effekt auf den Speicherplatz des Servers."
driveCapacityPerLocalAccount: "Drive-Kapazität pro lokales Benutzerkonto"
driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto"
driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen"
inMb: "In Megabytes"
iconUrl: "Icon-URL (favicon etc)"
bannerUrl: "Banner-URL"
backgroundImageUrl: "Hintergrundbild-URL"
basicInfo: "Basisdaten"
basicInfo: "Grundlegende Informationen"
pinnedUsers: "Angeheftete Benutzer"
pinnedUsersDescription: "Gib durch Leerzeichen getrennte Benutzer an, die an die \"Erkunden\"-Seite angeheftet werden sollen."
pinnedPages: "Angeheftete Seiten"
pinnedPagesDescription: "Gib durch Leerzeilen getrennte Pfäde zu Seiten an, die du an die Startseite dieser Instanz anheften möchtest.\n"
pinnedClipId: "ID des angehefteten Clips"
pinnedPagesDescription: "Gib durch Leerzeilen getrennte Pfäde zu Seiten an, die an die Startseite dieser Instanz angeheftet werden sollen.\n"
pinnedClipId: "ID des anzuheftenden Clips"
pinnedNotes: "Angeheftete Notizen"
hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptcha aktivieren"
@ -348,14 +346,14 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHA aktivieren"
recaptchaSiteKey: "Site key"
recaptchaSecretKey: "Secret key"
avoidMultiCaptchaConfirm: "Das Verwenden von mehreren Captcha-Systemen kann zu Störungen führen. Möchtest du die anderen Systeme deaktivieren? Du kannst mehrere Systeme aktiviert lassen, in dem du auf Abbrechen drückst."
avoidMultiCaptchaConfirm: "Das Verwenden von mehreren Captcha-Systemen kann zu Störungen führen. Sollen die anderen Systeme deaktiviert werden? Durch Abbrechen können mehrere Systeme aktiviert bleiben."
antennas: "Antennen"
manageAntennas: "Antennen verwalten"
name: "Name"
antennaSource: "Antennenquelle"
antennaKeywords: "Zu beobachtende Schlüsselwörter"
antennaExcludeKeywords: "Zu ignorierende Schlüsselwörter"
antennaKeywordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen"
antennaKeywordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen"
notifyAntenna: "Über neue Notizen benachrichtigen"
withFileAntenna: "Nur Notizen mit Dateien"
enableServiceworker: "ServiceWorker aktivieren"
@ -376,14 +374,14 @@ recentlyDiscoveredUsers: "Vor kurzem gefundene Benutzer"
exploreUsersCount: "Es gibt {count} Benutzer"
exploreFediverse: "Das Fediverse erkunden"
popularTags: "Beliebte Schlagwörter"
userList: "Listen"
userList: "Liste"
about: "Über"
aboutMisskey: "Über Misskey"
administrator: "Administrator"
token: "Token"
twoStepAuthentication: "Zwei-Faktor-Authentifizierung"
moderator: "Moderator"
nUsersMentioned: "{n} Benutzer reden darüber"
nUsersMentioned: "Von {n} Benutzern erwähnt"
securityKey: "Sicherheitsschlüssel"
securityKeyName: "Schlüsselname"
registerSecurityKey: "Sicherheitsschlüssel registrieren"
@ -391,7 +389,7 @@ lastUsed: "Zuletzt benutzt"
unregister: "Deaktivieren"
passwordLessLogin: "Passwortloses Anmelden einrichten"
resetPassword: "Passwort zurücksetzen"
newPasswordIs: "Das neue Passwort ist \"{password}\""
newPasswordIs: "Das neue Passwort ist „{password}“"
reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren"
share: "Teilen"
notFound: "Nicht gefunden"
@ -407,7 +405,7 @@ close: "Schließen"
group: "Gruppe"
groups: "Gruppen"
createGroup: "Gruppe erstellen"
ownedGroups: "Eigene Gruppen"
ownedGroups: "Meine Gruppen"
joinedGroups: "Beigetretene Gruppen"
invites: "Einladungen"
groupName: "Gruppenname"
@ -415,30 +413,29 @@ members: "Mitglieder"
transfer: "Übertragen"
messagingWithUser: "Privatchat"
messagingWithGroup: "Gruppenchat"
title: "Betreff"
title: "Titel"
text: "Text"
enable: "Aktivieren"
next: "Weiter"
retype: "Erneut eingeben"
noteOf: "Notiz von {user}"
inviteToGroup: "Zu Gruppe einladen"
maxNoteTextLength: "Maximale Länge von Notizen"
quoteAttached: "Zitiert"
quoteQuestion: "Als Zitat anfügen?"
noMessagesYet: "Noch keine Nachrichten"
quoteAttached: "Zitat"
quoteQuestion: "Als Zitat anhängen?"
noMessagesYet: "Noch keine Nachrichten vorhanden"
newMessageExists: "Du hast eine neue Nachricht"
onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden"
signinRequired: "Anmeldung erforderlich"
signinRequired: "Bitte melde dich an"
invitations: "Einladungen"
invitationCode: "Einladungscode"
checking: "Wird überprüft..."
checking: "Wird überprüft"
available: "Verfügbar"
unavailable: "Unverfügbar"
usernameInvalidFormat: "Klein- und Großbuchstaben, Zahlen sowie Unterstriche sind verwendbar."
usernameInvalidFormat: "Du kannst Klein- und Großbuchstaben, Zahlen sowie Unterstriche verwenden"
tooShort: "Zu kurz"
tooLong: "Zu lang"
weakPassword: "Schwaches Passwort"
normalPassword: "Normales Passwort"
normalPassword: "Durchschnittliches Passwort"
strongPassword: "Starkes Passwort"
passwordMatched: "Stimmt überein"
passwordNotMatched: "Stimmt nicht überein"
@ -454,18 +451,18 @@ useOsNativeEmojis: "Eingebaute Emojis des Betriebssystems benutzen"
disableDrawer: "Keine ausfahrbaren Menüs verwenden"
youHaveNoGroups: "Keine Gruppen vorhanden"
joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene."
noHistory: "Kein Verlauf"
noHistory: "Kein Verlauf gefunden"
signinHistory: "Anmeldungsverlauf"
disableAnimatedMfm: "MFM, die Animationen enthalten, deaktivieren"
doing: "In Bearbeitung..."
doing: "In Bearbeitung"
category: "Kategorie"
tags: "Schlagwörter"
docSource: "Quelle dieses Dokuments"
docSource: "Quellcode dieses Dokuments"
createAccount: "Benutzerkonto erstellen"
existingAccount: "Bestehendes Benutzerkonto"
regenerate: "Regenerieren"
fontSize: "Schriftgröße"
noFollowRequests: "Du hast keine ausstehende Follow-Anfragen"
noFollowRequests: "Keine ausstehenden Follow-Anfragen vorhanden"
openImageInNewTab: "Bilder in neuem Tab öffnen"
dashboard: "Dashboard"
local: "Lokal"
@ -476,25 +473,25 @@ dayOverDayChanges: "Veränderung zu Gestern"
appearance: "Aussehen"
clientSettings: "Client-Einstellungen"
accountSettings: "Benutzerkonto-Einstellungen"
promotion: "Hervorgehoben"
promote: "Hervorheben"
promotion: "Werbung"
promote: "Werbung schalten"
numberOfDays: "Anzahl der Tage"
hideThisNote: "Diese Notiz verstecken"
showFeaturedNotesInTimeline: "Beliebte Notizen in Chronik anzeigen"
showFeaturedNotesInTimeline: "Beliebte Notizen in der Chronik anzeigen"
objectStorage: "Objektspeicher"
useObjectStorage: "Objektspeicher verwenden"
objectStorageBaseUrl: "Basis-URL"
objectStorageBaseUrlDesc: "Als Referenz verwendete URL. Verwendest du einen CDN oder Proxy, gib dessen URL an. S3: 'https://<bucket>.s3.amazonaws.com', GCS: 'https://storage.googleapis.com/<bucket>' etc."
objectStorageBaseUrlDesc: "Die als Referenz verwendete URL. Verwendest du einen CDN oder Proxy, gib dessen URL an. Für S3 verwende 'https://<bucket>.s3.amazonaws.com'. Für GCS o.ä. verwende 'https://storage.googleapis.com/<bucket>'."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Bitte gib den Bucket-Namen an, der bei deinem Anbieter verwendet wird."
objectStorageBucketDesc: "Bitte gib den Namen des Buckets an, der bei deinem Anbieter verwendet wird."
objectStoragePrefix: "Prefix"
objectStoragePrefixDesc: "Dateien werden in Ordnern unter diesem Prefix gespeichert."
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten Endpoint im Format \"<host>\" oder \"<host>:<port>\" angeben."
objectStorageEndpointDesc: "Im Falle von S3 leerlassen, für andere Anbieter den relevanten Endpoint im Format „<host>“ oder „<host>:<port>“ angeben."
objectStorageRegion: "Region"
objectStorageRegionDesc: "Gib eine Region wie z.B. \"xx-east-1\" an. Falls dein Anbieter nicht zwischen Regionen unterscheidet, lass dieses Feld leer oder gib \"us-east-1\" an."
objectStorageRegionDesc: "Gib eine Region wie z.B. „xx-east-1“ an. Falls dein Anbieter nicht zwischen Regionen unterscheidet, lass dieses Feld leer oder gib „us-east-1“ an."
objectStorageUseSSL: "SSL verwenden"
objectStorageUseSSLDesc: "Deaktiviere dies, falls du für die API-Verbindungen kein HTTPS verwenden wirst"
objectStorageUseSSLDesc: "Deaktiviere dies, falls du für API-Verbindungen kein HTTPS verwenden wirst"
objectStorageUseProxy: "Über Proxy verbinden"
objectStorageUseProxyDesc: "Deaktiviere dies, falls du keinen Proxy für den Objektspeicher verwenden wirst"
objectStorageSetPublicRead: "Bei Upload auf \"public-read\" stellen"
@ -505,7 +502,7 @@ newNoteRecived: "Es gibt neue Notizen"
sounds: "Töne"
listen: "Anhören"
none: "Nichts"
showInPage: "In Seite anzeigen"
showInPage: "In einer Seite anzeigen"
popout: "Pop-Up"
volume: "Lautstärke"
masterVolume: "Gesamtlautstärke"
@ -524,7 +521,7 @@ sort: "Sortieren"
ascendingOrder: "Aufsteigende Reihenfolge"
descendingOrder: "Absteigende Reihenfolge"
scratchpad: "Testumgebung"
scratchpadDescription: "Die Testumgebung bietet eine Umgebung für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen."
scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen."
output: "Ausgabe"
script: "Skript"
disablePagesScript: "AiScript auf Seiten deaktivieren"
@ -547,14 +544,14 @@ addedRelays: "Hinzugefügte Relays"
serviceworkerInfo: "Muss für Push-Benachrichtigungen aktiviert sein."
deletedNote: "Gelöschte Notiz"
invisibleNote: "Private Notiz"
enableInfiniteScroll: "Automatisch mehr Notizen laden"
enableInfiniteScroll: "Automatisch mehr laden"
visibility: "Sichtbarkeit"
poll: "Umfrage"
useCw: "Inhaltswarnung verwenden"
enablePlayer: "Video-Player öffnen"
disablePlayer: "Video-Player schließen"
expandTweet: "Tweet ausklappen"
themeEditor: "Farbthemen-Editor"
themeEditor: "Farbschema-Editor"
description: "Beschreibung"
describeFile: "Beschreibung hinzufügen"
enterFileDescription: "Beschreibung eingeben"
@ -590,13 +587,13 @@ smtpHost: "Host"
smtpPort: "Port"
smtpUser: "Benutzername"
smtpPass: "Passwort"
emptyToDisableSmtpAuth: "Benutzername und Passwort leer lassen um SMTP-Verifizierung zu deaktivieren"
emptyToDisableSmtpAuth: "Benutzername und Passwort leer lassen, um SMTP-Verifizierung zu deaktivieren"
smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden"
smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest"
testEmail: "Email-Versand testen"
wordMute: "Wort-Stummschaltung"
regexpError: "Regular Expression error"
regexpErrorDescription: "Error in the regular expression on line {line} in your {tab} word mutes:"
smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest."
testEmail: "Emailversand testen"
wordMute: "Wortstummschaltung"
regexpError: "Fehler in einem regulären Ausdruck"
regexpErrorDescription: "Im regulären Ausdruck deiner {tab}en Wortstummschaltungen ist ein Fehler aufgetreten:"
instanceMute: "Instanzstummschaltungen"
userSaysSomething: "{name} hat etwas gesagt"
makeActive: "Aktivieren"
@ -610,11 +607,11 @@ database: "Datenbank"
channel: "Kanäle"
create: "Erstellen"
notificationSetting: "Benachrichtigungseinstellungen"
notificationSettingDesc: "Wähle die Art der anzuzeigenden Benachrichtigung"
notificationSettingDesc: "Wähle die Art der anzuzeigenden Benachrichtigungen."
useGlobalSetting: "Globale Einstellung verwenden"
useGlobalSettingDesc: "Ist dies eingeschaltet, so werden die Benachrichtigungseinstellungen deines Benutzerkontos verwendet. Wenn dies ausgeschaltet ist, können individuelle Einstellungen vorgenommen werden."
other: "Andere"
regenerateLoginToken: "Anmeldungstoken regenerieren"
useGlobalSettingDesc: "Ist diese Option aktiviert, werden die Benachrichtigungseinstellungen deines Benutzerkontos verwendet. Durch ausschalten dieser Option können individuelle Einstellungen vorgenommen werden."
other: "Anderes"
regenerateLoginToken: "Anmeldetoken regenerieren"
regenerateLoginTokenDescription: "Den zur Anmeldung intern verwendeten Token regenerieren. Normalerweise wird dies nicht benötigt. Bei Regeneration werden alle Geräte ausgeloggt."
setMultipleBySeparatingWithSpace: "Trenne Elemente durch ein Leerzeichen um mehrere Einstellungen zu kofigurieren."
fileIdOrUrl: "Datei-ID oder URL"
@ -624,7 +621,7 @@ abuseReports: "Meldungen"
reportAbuse: "Melden"
reportAbuseOf: "{name} melden"
fillAbuseReportDescription: "Bitte gib zusätzliche Informationen zu dieser Meldung an. Falls es sich um eine spezielle Notiz handelt, bitte gib dessen URL an."
abuseReported: "Die Meldung wurde versendet. Vielen Dank."
abuseReported: "Deine Meldung wurde versendet. Vielen Dank."
reporter: "Melder"
reporteeOrigin: "Herkunft des Gemeldeten"
reporterOrigin: "Herkunft des Meldenden"
@ -637,18 +634,18 @@ openInSideView: "In Seitenansicht öffnen"
defaultNavigationBehaviour: "Standardnavigationsverhalten"
editTheseSettingsMayBreakAccount: "Bei Bearbeitung dieser Einstellungen besteht die Gefahr, dein Benutzerkonto zu beschädigen."
instanceTicker: "Instanz-Informationen von Notizen"
waitingFor: "Warte auf {x}"
waitingFor: "Warte auf {x}"
random: "Zufällig"
system: "System"
switchUi: "UI wechseln"
desktop: "Desktop"
clip: "Clip"
clip: "Clip erstellen"
createNew: "Neu erstellen"
optional: "Optional"
createNewClip: "Neuen Clip erstellen"
public: "Öffentlich"
i18nInfo: "Misskey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen."
manageAccessTokens: "Zugriffstoken verwalten"
manageAccessTokens: "Zugriffstokens verwalten"
accountInfo: "Benutzerkonto-Informationen"
notesCount: "Anzahl der Notizen"
repliesCount: "Anzahl gesendeter Antworten"
@ -663,15 +660,15 @@ pollVotesCount: "Anzahl gesendeter Antworten auf Umfragen"
pollVotedCount: "Anzahl erhaltener Antworten auf Umfragen"
yes: "Ja"
no: "Nein"
driveFilesCount: "Anzahl an Drive-Dateien"
driveFilesCount: "Anzahl der Dateien in Drive"
driveUsage: "Drive-Auslastung"
noCrawle: "Crawler-Indexierung ablehnen"
noCrawleDescription: "Suchmaschinen bitten, die eigene Profilseite, Notizen, Seiten usw. nicht zu indexieren"
lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt, wird jeder deine Notizen öffentlich sehen können, sofern du die Notizsichtbarkeit nicht auf \"Nur Follower\" setzt."
noCrawleDescription: "Suchmaschinen bitten, die eigene Profilseite, Notizen, Seiten usw. nicht zu indexieren."
lockedAccountInfo: "Auch wenn du Follow-Anfragen auf manuelle Bestätigung setzt, wird jede deiner Notizen öffentlich sichtbar sein, sofern du ihre Notizsichtbarkeit nicht auf \"Nur Follower\" setzt."
alwaysMarkSensitive: "Medien standardmäßig als NSFW markieren"
loadRawImages: "Anstatt Vorschaubilder immer Originalbilder anzeigen"
disableShowingAnimatedImages: "Animierte Bilder nicht abspielen"
verificationEmailSent: "Eine Verifizierungsnachricht wurde versendet. Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
verificationEmailSent: "Eine Bestätigungsmail wurde an deine Email-Adresse versendet. Besuche den dort enthaltenen Link, um die Verifizierung abzuschließen."
notSet: "Nicht konfiguriert"
emailVerified: "Email-Adresse bestätigt"
noteFavoritesCount: "Anzahl an als Favorit markierter Notizen"
@ -682,12 +679,12 @@ useSystemFont: "Standardschriftart des Systems verwenden"
clips: "Clips"
experimentalFeatures: "Experimentelle Funktionalitäten"
developer: "Entwickler"
makeExplorable: "Benutzerkonto in \"Erkunden\" sichtbar machen"
makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im \"Erkunden\"-Bereich sichtbar."
makeExplorable: "Benutzerkonto in „Erkunden“ sichtbar machen"
makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im „Erkunden“-Bereich sichtbar."
showGapBetweenNotesInTimeline: "Abstände zwischen Notizen auf der Chronik anzeigen"
duplicate: "Duplizieren"
left: "Links"
center: "Mitte"
center: "Mittig"
wide: "Breit"
narrow: "Schmal"
reloadToApplySetting: "Diese Einstellung tritt nach einer Aktualisierung der Seite in Kraft. Jetzt aktualisieren?"
@ -698,19 +695,19 @@ onlineUsersCount: "{n} Benutzer sind online"
nUsers: "{n} Benutzer"
nNotes: "{n} Notizen"
sendErrorReports: "Fehlerberichte senden"
sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Misskey weitergegeben, was zur Verbesserung der Qualität von Misskey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten."
myTheme: "Mein Farbthema"
sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Misskey weitergegeben, was zur Verbesserung der Qualität von Misskey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten innerhalb Misskey."
myTheme: "Mein Farbschema"
backgroundColor: "Hintergrundfarbe"
accentColor: "Akzentfarbe"
textColor: "Textfarbe"
saveAs: "Speichern als…"
saveAs: "Speichern als …"
advanced: "Fortgeschritten"
value: "Wert"
createdAt: "Erstellt am"
updatedAt: "Zuletzt geändert am"
saveConfirm: "Änderungen speichern?"
deleteConfirm: "Wirklich löschen?"
invalidValue: "Ungültiger Wert."
invalidValue: "Dieser Wert ist ungültig."
registry: "Registry"
closeAccount: "Benutzerkonto schließen"
currentVersion: "Momentane Version"
@ -723,11 +720,11 @@ inUse: "Verwendet"
editCode: "Code bearbeiten"
apply: "Anwenden"
receiveAnnouncementFromInstance: "Benachrichtigungen von dieser Instanz empfangen"
emailNotification: "E-Mail-Benachrichtigungen"
emailNotification: "Email-Benachrichtigungen"
publish: "Veröffentlichen"
inChannelSearch: "In Kanal suchen"
useReactionPickerForContextMenu: "Reaktionsauswahl durch Rechtsklick öffnen"
typingUsers: "{users} ist/sind am schreiben..."
typingUsers: "{users} ist/sind am schreiben"
jumpToSpecifiedDate: "Zu bestimmtem Datum springen"
showingPastTimeline: "Es wird eine alte Chronik angezeigt"
clear: "Zurückkehren"
@ -737,19 +734,19 @@ unlikeConfirm: "\"Gefällt mir\" wirklich entfernen?"
fullView: "Vollansicht"
quitFullView: "Vollansicht verlassen"
addDescription: "Beschreibung hinzufügen"
userPagePinTip: "Um Notizen hier erscheinen zu lassen, drücke \"Anheften\" im Menü individueller Notizen."
userPagePinTip: "Um Notizen hier erscheinen zu lassen, drücke \"An dein Profil anheften\" im Menü individueller Notizen."
notSpecifiedMentionWarning: "Diese Notiz enthält Erwähnungen von Nutzern, die nicht als Empfänger ausgewählt sind"
info: "Über"
userInfo: "Benutzerinformation"
unknown: "Unbekannt"
onlineStatus: "Online-Status"
hideOnlineStatus: "Online-Status verbergen"
hideOnlineStatusDescription: "Das Verbergen deines Online-Statuses reduziert die Nützlichkeit von Funktionen wie der Suche."
onlineStatus: "Onlinestatus"
hideOnlineStatus: "Onlinestatus verbergen"
hideOnlineStatusDescription: "Das Verbergen deines Onlinestatuses reduziert die Nützlichkeit von Funktionen wie der Suche."
online: "Online"
active: "Aktiv"
offline: "Offline"
notRecommended: "Nicht empfohlen"
botProtection: "Bot-Schutz"
botProtection: "Schutz vor Bots"
instanceBlocking: "Blockierte Instanzen"
selectAccount: "Benutzerkonto auswählen"
switchAccount: "Konto wechseln"
@ -761,9 +758,9 @@ administration: "Verwaltung"
accounts: "Benutzerkonten"
switch: "Wechseln"
noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert."
noBotProtectionWarning: "Bot-Schutz ist nicht konfiguriert."
noBotProtectionWarning: "Schutz vor Bots ist nicht konfiguriert."
configure: "Konfigurieren"
postToGallery: "Neuen Galerie-Beitrag erstellen"
postToGallery: "Neuen Galeriebeitrag erstellen"
gallery: "Galerie"
recentPosts: "Neue Beiträge"
popularPosts: "Beliebte Beiträge"
@ -775,7 +772,7 @@ priority: "Priorität"
high: "Hoch"
middle: "Mittel"
low: "Niedrig"
emailNotConfiguredWarning: "Keine Email-Adresse hinterlegt"
emailNotConfiguredWarning: "Keine Email-Adresse hinterlegt."
ratio: "Verhältnis"
previewNoteText: "Vorschau anzeigen"
customCss: "Benutzerdefiniertes CSS"
@ -793,9 +790,9 @@ misskeyUpdated: "Misskey wurde aktualisiert!"
whatIsNew: "Änderungen anzeigen"
translate: "Übersetzen"
translatedFrom: "Aus {x} übersetzt"
accountDeletionInProgress: "Löschung des Benutzerkontos momentan in Bearbeitung"
accountDeletionInProgress: "Die Löschung deines Benutzerkontos ist momentan in Bearbeitung."
usernameInfo: "Ein Name, durch den dein Benutzerkonto auf diesem Server identifiziert werden kann. Du kannst das Alphabet (a~z, A~Z), Ziffern (0~9) oder Unterstriche (_) verwenden. Benutzernamen können später nicht geändert werden."
aiChanMode: "Ai Modus"
aiChanMode: "Ai-Modus"
keepCw: "Inhaltswarnungen beibehalten"
pubSub: "Pub/Sub Benutzerkonten"
lastCommunication: "Letzte Kommunikation"
@ -804,7 +801,7 @@ unresolved: "Ungelöst"
breakFollow: "Follower entfernen"
itsOn: "Eingeschaltet"
itsOff: "Ausgeschaltet"
emailRequiredForSignup: "Angaben einer Email-Adresse als benötigt markieren"
emailRequiredForSignup: "Angabe einer Email-Adresse als benötigt markieren"
unread: "Ungelesen"
filter: "Filter"
controlPanel: "Systemsteuerung"
@ -819,10 +816,10 @@ ffVisibilityDescription: "Konfiguriere wer sehen kann, wem du folgst sowie wer d
continueThread: "Weiteren Threadverlauf anzeigen"
deleteAccountConfirm: "Dein Benutzerkonto wird unwiderruflich gelöscht. Trotzdem fortfahren?"
incorrectPassword: "Falsches Passwort."
voteConfirm: "Wirklich für \"{choice}\" abstimmen?"
voteConfirm: "Wirklich für „{choice}“ abstimmen?"
hide: "Inhalt verbergen"
leaveGroup: "Gruppe verlassen"
leaveGroupConfirm: "Möchtest du \"{name}\" wirklich verlassen?"
leaveGroupConfirm: "Möchtest du „{name}“ wirklich verlassen?"
useDrawerReactionPickerForMobile: "Auf mobilen Geräten ausfahrbare Reaktionsauswahl anzeigen"
welcomeBackWithName: "Willkommen zurück, {name}"
clickToFinishEmailVerification: "Drücke bitte auf [{ok}], um die Email-Bestätigung abzuschließen."
@ -833,6 +830,16 @@ auto: "Automatisch"
themeColor: "Instanzfarbe"
size: "Größe"
numberOfColumn: "Spaltenanzahl"
searchByGoogle: "Googlen"
instanceDefaultLightTheme: "Instanzweites Standardfarbschema (Hell)"
instanceDefaultDarkTheme: "Instanzweites Standardfarbschema (Dunkel)"
instanceDefaultThemeDescription: "Gib den Farbschemencode im Objektformat ein."
mutePeriod: "Stummschaltungsdauer"
indefinitely: "Dauerhaft"
tenMinutes: "10 Minuten"
oneHour: "Eine Stunde"
oneDay: "Einen Tag"
oneWeek: "Eine Woche"
_emailUnavailable:
used: "Diese Email-Adresse wird bereits verwendet"
format: "Das Format dieser Email-Adresse ist ungültig"
@ -845,20 +852,20 @@ _ffVisibility:
private: "Privat"
_signup:
almostThere: "Fast geschafft"
emailAddressInfo: "Bitte gib deine Email-Adresse ein."
emailAddressInfo: "Bitte gib deine Email-Adresse ein. Sie wird nicht öffentlich einsehbar sein."
emailSent: "An deine Email-Adresse ({email}) wurde soeben eine Bestätigungsmail geschickt. Bitte klicke auf den enthaltenen Link, um die Erstellung deines Benutzerkontos abzuschließen."
_accountDelete:
accountDelete: "Benutzerkonto löschen"
mayTakeTime: "Da die Löschung eines Benutzerkontos ein aufwendiger Prozess ist, kann dessen Dauer davon abhängen, wie viel Inhalt in diesem erstellt wurde oder wie viele Dateien hochgeladen wurden."
mayTakeTime: "Da die Löschung eines Benutzerkontos ein aufwendiger Prozess ist, kann dessen Dauer davon abhängen, wie viel Inhalt von diesem erstellt wurde oder wie viele Dateien von diesem hochgeladen wurden."
sendEmail: "Sobald die Löschung abgeschlossen ist, wird an die mit ihm verknüpfte Email-Adresse eine Benachrichtigung versendet."
requestAccountDelete: "Löschung des Benutzerkontos anfordern"
started: "Löschung wurde eingeleitet."
requestAccountDelete: "Löschung deines Benutzerkontos anfordern"
started: "Die Löschung wurde eingeleitet."
inProgress: "Löschung in Bearbeitung"
_ad:
back: "Zurück"
reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen"
_forgotPassword:
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit der du dein Passwort zurücksetzen kannst."
enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
ifNoEmail: "Solltest du bei der Registrierung keine Email-Adresse angegeben haben, wende dich bitte an den Administrator."
contactAdmin: "Diese Instanz unterstützt die Verwendung von Email-Adressen nicht. Wende dich an den Administrator, um dein Passwort zurückzusetzen."
_gallery:
@ -882,7 +889,7 @@ _registry:
domain: "Domain"
createKey: "Schlüssel erstellen"
_aboutMisskey:
about: "Misskey ist Open-Source-Software die von syuilo seit 2014 entwickelt wird."
about: "Misskey ist Open-Source-Software, welche von syuilo seit 2014 entwickelt wird."
contributors: "Hauptmitwirkende"
allContributors: "Alle Mitwirkenden"
source: "Quellcode"
@ -899,13 +906,13 @@ _mfm:
intro: "MFM ist eine Misskey-exklusive Markup-Sprache, die in Misskey an vielen Stellen verwendet werden kann. Hier kannst du eine Liste von verfügbarer MFM-Syntax einsehen."
dummy: "Misskey erweitert die Welt des Fediverse"
mention: "Erwähnung"
mentionDescription: "Mit At-Zeichen und Nutzername kann ein individueller Nutzer angegeben werden."
mentionDescription: "Mit At-Zeichen und Benutzername kann ein individueller Nutzer angegeben werden."
hashtag: "Hashtag"
hashtagDescription: "Mit einer Raute und Text kann ein Hashtag angegeben werden."
url: "URL"
urlDescription: "Zeigt URLs an."
link: "Link"
linkDescription: "Spezifische Textabschnitte als URL anzeigen."
linkDescription: "Zeigt spezifische Textabschnitte als URL an."
bold: "Fett"
boldDescription: "Zeichen zur Betonung dicker erscheinen lassen."
small: "Klein"
@ -929,19 +936,19 @@ _mfm:
flip: "Spiegelung"
flipDescription: "Inhalt horizontal oder vertikal gespiegelt anzeigen."
jelly: "Animation (Dehnen)"
jellyDescription: "Verleiht dem Inhalt eine sich dehnende Animation."
jellyDescription: "Verleiht Inhalt eine sich dehnende Animation."
tada: "Animation (Tada)"
tadaDescription: "Verleiht eine Animation mit \"Tada!\"-Gefühl"
tadaDescription: "Verleiht Inhalt eine Animation mit \"Tada!\"-Gefühl"
jump: "Animation (Sprung)"
jumpDescription: "Verleiht dem Inhalt eine springende Animation."
jumpDescription: "Verleiht Inhalt eine springende Animation."
bounce: "Animation (Federn)"
bounceDescription: "Verleiht dem Inhalt eine federnde Animation."
bounceDescription: "Verleiht Inhalt eine federnde Animation."
shake: "Animation (Zittern)"
shakeDescription: "Verleiht dem Inhalt eine zitternde Animation."
shakeDescription: "Verleiht Inhalt eine zitternde Animation."
twitch: "Animation (Zucken)"
twitchDescription: "Verleiht dem Inhalt eine sehr stark zuckende Animation."
twitchDescription: "Verleiht Inhalt eine sehr stark zuckende Animation."
spin: "Animation (Rotieren)"
spinDescription: "Verleiht dem Inhalt eine rotierende Animation."
spinDescription: "Verleiht Inhalt eine rotierende Animation."
x2: "Groß"
x2Description: "Inhalte größer anzeigen."
x3: "Sehr groß"
@ -949,7 +956,7 @@ _mfm:
x4: "Unglaublich groß"
x4Description: "Lässt Inhalte noch größer als größer als groß angezeigt werden."
blur: "Weichzeichnen"
blurDescription: "Inhalte durch Weihzeichnung verschwimmen lassen. Durch das Bewegen des Mauszeigers auf den Inhalt wird er klar angezeigt."
blurDescription: "Inhalte durch Weihzeichnung verschwimmen lassen. Durch das Bewegen des Mauszeigers über den Inhalt wird er klar angezeigt."
font: "Schriftart"
fontDescription: "Setzt die Schriftart des Inhaltes fest."
rainbow: "Regenbogen"
@ -957,7 +964,7 @@ _mfm:
sparkle: "Glitzer"
sparkleDescription: "Verleiht Inhalt einen glitzernden Partikeleffekt."
rotate: "Drehen"
rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel"
rotateDescription: "Dreht den Inhalt um einen angegebenen Winkel."
_instanceTicker:
none: "Nie anzeigen"
remote: "Für Benutzer fremder Instanzen anzeigen"
@ -983,7 +990,7 @@ _menuDisplay:
hide: "Ausblenden"
_wordMute:
muteWords: "Stummgeschaltete Wörter"
muteWordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen."
muteWordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen."
muteWordsDescription2: "Umgib Schlüsselworter mit Schrägstrichen, um Reguläre Ausdrücke zu verwenden."
softDescription: "Notizen, die die angegebenen Konditionen erfüllen, in der Chronik ausblenden."
hardDescription: "Verhindern, dass Notizen, die die angegebenen Konditionen erfüllen, der Chronik hinzugefügt werden. Zudem werden diese Notizen auch nicht der Chronik hinzugefügt, falls die Konditionen geändert werden."
@ -996,17 +1003,17 @@ _instanceMute:
title: "Blendet Notizen von stummgeschalteten Instanzen aus."
heading: "Liste der stummzuschaltenden Instanzen"
_theme:
explore: "Themen erforschen"
install: "Thema installieren"
manage: "Themaverwaltung"
code: "Themencode"
explore: "Farbschemata erforschen"
install: "Farbschmata installieren"
manage: "Farbschemaverwaltung"
code: "Farbschemencode"
description: "Beschreibung"
installed: "{name} wurde installiert"
installedThemes: "Installierte Themen"
builtinThemes: "Eingebaute Themen"
alreadyInstalled: "Dieses Thema ist bereits installiert"
invalid: "Der Themencode dieses Themas ist ungültig"
make: "Farbthema erstellen"
installedThemes: "Installierte Farbschemata"
builtinThemes: "Eingebaute Farbschemata"
alreadyInstalled: "Dieses Farbschema ist bereits installiert"
invalid: "Der Code dieses Farbschemas ist ungültig"
make: "Farbschema erstellen"
base: "Vorlage"
addConstant: "Konstante hinzufügen"
constant: "Konstante"
@ -1023,7 +1030,7 @@ _theme:
darken: "Verdunkeln"
lighten: "Erhellen"
inputConstantName: "Name der Konstanten eingeben"
importInfo: "Du kannst hier Themencode einfügen, um ihn in den Editor zu importieren"
importInfo: "Hier kannst du Farbschemencode einfügen, um ihn in den Editor zu importieren"
deleteConstantConfirm: "Die Konstante {const} wirklich löschen?"
keys:
accent: "Akzentfarbe"
@ -1060,7 +1067,7 @@ _theme:
toastFg: "Text von Benachrichtigungen"
buttonBg: "Hintergrund von Schaltflächen"
buttonHoverBg: "Hintergrund von Schaltflächen (Mouseover)"
inputBorder: "Rahmen des Eingabefelds"
inputBorder: "Rahmen von Eingabefeldern"
listItemHoverBg: "Hintergrund von Listeneinträgen (Mouseover)"
driveFolderBg: "Hintergrund von Drive-Ordnern"
wallpaperOverlay: "Hintergrundbild-Overlay"
@ -1101,7 +1108,7 @@ _tutorial:
step2_1: "Lass uns zuerst dein Profil vervollständigen, bevor du Notizen schreibst oder jemandem folgst."
step2_2: "Informationen darüber, was für eine Person du bist, macht es anderen leichter zu wissen, ob sie deine Notizen sehen wollen und ob sie dir folgen möchten."
step3_1: "Mit dem Einrichten deines Profils fertig?"
step3_2: "Dann lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf das Stift-Icon oben auf dem Bildschirm drückst."
step3_2: "Dann lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon auf dem Bildschirm drückst."
step3_3: "Fülle das Fenster aus und drücke auf den Knopf oben rechts zum Senden."
step3_4: "Fällt dir nichts ein, das du schreiben möchtest? Versuch's mit \"Hallo Misskey!\""
step4_1: "Fertig mit dem Senden deiner ersten Notiz?"
@ -1111,8 +1118,8 @@ _tutorial:
step5_3: "Klicke zum Anzeigen des Profils eines Benutzers auf dessen Profilbild und dann auf den \"Folgen\"-Knopf, um diesem zu folgen."
step5_4: "Je nach Benutzer kann es etwas Zeit in Anspruch nehmen, bis dieser deine Follow-Anfrage bestätigt."
step6_1: "Wenn du nun auch die Notizen anderer Benutzer in deiner Chronik siehst, hast du auch diesmal alles richtig gemacht."
step6_2: "Du kannst ebenso \"Reaktionen\" verwenden, um schnell auf Notizen anderer Benutzer zu reagieren."
step6_3: "Um eine Reaktion anzufügen, klicke auf das \"+\"-Symbol in der Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest."
step6_2: "Du kannst ebenso „Reaktionen“ verwenden, um schnell auf Notizen anderer Benutzer zu reagieren."
step6_3: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol in der Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest."
step7_1: "Glückwunsch! Du hast die Einführung in die Verwendung von Misskey abgeschlossen."
step7_2: "Wenn du mehr über Misskey lernen möchtest, schau dich im {help}-Bereich um."
step7_3: "Und nun, viel Spaß mit Misskey! 🚀"
@ -1159,7 +1166,7 @@ _permissions:
"read:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge lesen"
"write:gallery-likes": "Liste deiner mit \"Gefällt mir\" markierten Galerie-Beiträge bearbeiten"
_auth:
shareAccess: "Möchtest du \"{name}\" authorisieren, auf dieses Benuzerkonto zugreifen zu können?"
shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?"
shareAccessAsk: "Bist du dir sicher, dass du diese Anwendung authorisieren möchtest, auf dein Benutzerkonto zugreifen zu können?"
permissionAsk: "Diese Anwendung fordert folgende Berechtigungen"
pleaseGoBack: "Bitte kehre zur Anwendung zurück"
@ -1180,7 +1187,7 @@ _weekday:
friday: "Freitag"
saturday: "Samstag"
_widgets:
memo: "Memo"
memo: "Merkzettel"
notifications: "Benachrichtigungen"
timeline: "Chronik"
calendar: "Kalender"
@ -1211,8 +1218,8 @@ _poll:
canMultipleVote: "Auswahl mehrerer Antworten erlauben"
expiration: "Abstimmung beenden"
infinite: "Nie"
at: "Beenden am..."
after: "Beenden nach..."
at: "Beenden am"
after: "Beenden nach"
deadlineDate: "Enddatum"
deadlineTime: "Zeit"
duration: "Dauer"
@ -1238,24 +1245,24 @@ _visibility:
localOnly: "Nur Lokal"
localOnlyDescription: "Unsichtbar für Benutzer anderer Instanzen"
_postForm:
replyPlaceholder: "Dieser Notiz antworten..."
quotePlaceholder: "Diese Notiz zitieren..."
replyPlaceholder: "Dieser Notiz antworten"
quotePlaceholder: "Diese Notiz zitieren"
channelPlaceholder: "In einen Kanal senden"
_placeholders:
a: "Was machst du momentan?"
b: "Was ist um dich herum los?"
c: "Was geht dir durch den Kopf?"
d: "Was möchtest du sagen?"
e: "Fang an zu schreiben..."
f: "Ich warte darauf, dass du schreibst..."
e: "Fang an zu schreiben"
f: "Ich warte darauf, dass du schreibst"
_profile:
name: "Name"
username: "Benutzername"
description: "Über mich"
youCanIncludeHashtags: "Du kannst auch Hashtags in deiner Beschreibung verwenden."
description: "Profilbeschreibung"
youCanIncludeHashtags: "Du kannst auch Hashtags in deiner Profilbeschreibung verwenden."
metadata: "Zusätzliche Informationen"
metadataEdit: "Zusätzliche Informationen bearbeiten"
metadataDescription: "Du kannst auf deinem Profil vier zusätzliche Informationsblöcke anzeigen lassen."
metadataDescription: "Hierdurch kannst du auf deinem Profil zusätzliche Informationsblöcke anzeigen lassen."
metadataLabel: "Beschriftung"
metadataContent: "Inhalt"
changeAvatar: "Profilbild ändern"
@ -1274,25 +1281,25 @@ _charts:
usersIncDec: "Unterschied in der Anzahl von Benutzern"
usersTotal: "Anzahl aller Benutzer"
activeUsers: "Aktive Benutzer"
notesIncDec: "Unterschied in der Anzahl von Notizen"
localNotesIncDec: "Unterschied in der Anzahl von lokalen Notizen"
remoteNotesIncDec: "Unterschied in Anzahl der Notizen von fremden Instanzen"
notesIncDec: "Unterschied in der Anzahl an Notizen"
localNotesIncDec: "Unterschied in der Anzahl an lokalen Notizen"
remoteNotesIncDec: "Unterschied in der Anzahl an Notizen von fremden Instanzen"
notesTotal: "Anzahl aller Notizen"
filesIncDec: "Unterschied an Dateien"
filesTotal: "Summe der Dateien"
filesIncDec: "Unterschied in der Anzahl an Dateien"
filesTotal: "Anzahl aller Dateien"
storageUsageIncDec: "Unterschied in der Höhe der Speichernutzung"
storageUsageTotal: "Gesamte Speichernutzung"
_instanceCharts:
requests: "Anfragen"
users: "Unterschied in der Anzahl von Benutzern"
users: "Unterschied in der Anzahl an Benutzern"
usersTotal: "Gesamtanzahl an Benutzern"
notes: "Unterschied in der Anzahl von Notizen"
notes: "Unterschied in der Anzahl an Notizen"
notesTotal: "Gesamtanzahl an Notizen"
ff: "Unterschied in der Anzahl von gefolgten Benutzern und Followern"
ff: "Unterschied in der Anzahl an gefolgten Benutzern und Followern"
ffTotal: "Gesamtanzahl an gefolgten Benutzern und Followern"
cacheSize: "Unterschied in der Größe des Caches"
cacheSizeTotal: "Gesamtgröße des Caches"
files: "Unterschied in der Anzahl der Dateien"
files: "Unterschied in der Anzahl an Dateien"
filesTotal: "Gesamtanzahl an Dateien"
_timelines:
home: "Startseite"
@ -1302,7 +1309,7 @@ _timelines:
_pages:
newPage: "Seite erstellen"
editPage: "Seite bearbeiten"
readPage: "Quelltext-Ansicht"
readPage: "Quelltextansicht"
created: "Seite erfolgreich erstellt"
updated: "Seite erfolgreich aktualisiert"
deleted: "Seite erfolgreich gelöscht"
@ -1326,7 +1333,7 @@ _pages:
url: "Seiten-URL"
summary: "Zusammenfassung"
alignCenter: "Zentrieren"
hideTitleWhenPinned: "Seitentitel ausblenden, wenn angeheftet"
hideTitleWhenPinned: "Seitentitel wenn angeheftet ausblenden"
font: "Schriftart"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
@ -1363,7 +1370,7 @@ _pages:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
numberInput: "Nummereingabe"
numberInput: "Zahleneingabe"
_numberInput:
name: "Variablenname"
text: "Titel"
@ -1387,7 +1394,7 @@ _pages:
_counter:
name: "Variablenname"
text: "Titel"
inc: "Erhöhen um"
inc: "Schrittgröße"
_button:
text: "Titel"
colored: "Farbig"
@ -1433,10 +1440,10 @@ _pages:
strLen: "Textlänge"
_strLen:
arg1: "Text"
strPick: "Zeichen extrahieren"
strPick: "Text extrahieren"
_strPick:
arg1: "Text"
arg2: "Zeichenposition"
arg2: "Textposition"
strReplace: "Textersetzung"
_strReplace:
arg1: "Text"
@ -1447,7 +1454,7 @@ _pages:
arg1: "Text"
join: "Text zusammenfügen"
_join:
arg1: "Listen"
arg1: "Liste"
arg2: "Trennzeichen"
add: "Addieren"
_add:
@ -1576,7 +1583,7 @@ _pages:
_for:
arg1: "Anzahl der Schleifendurchläufe"
arg2: "Aktion"
typeError: "Slot {slot} akzeptiert Werte vom Typ \"{expect}\", aber es wurde ein \"{actual}\" Wert angegeben!"
typeError: "Slot {slot} akzeptiert Werte vom Typ „{expect}“, aber es wurde ein „{actual}“ Wert angegeben!"
thereIsEmptySlot: "Slot {slot} ist leer!"
types:
string: "Text"
@ -1587,7 +1594,7 @@ _pages:
emptySlot: "Leerer Slot"
enviromentVariables: "Umgebungsvariable"
pageVariables: "Seitenelemente"
argVariables: "Eingabe-Slots"
argVariables: "Eingabeslots"
_relayStatus:
requesting: "Ausstehend"
accepted: "Akzeptiert"
@ -1605,6 +1612,7 @@ _notification:
youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen"
pollEnded: "Umfrageergebnisse sind verfügbar"
_types:
all: "Alle"
follow: "Neue Follower"
@ -1614,6 +1622,7 @@ _notification:
quote: "Zitationen"
reaction: "Reaktionen"
pollVote: "Antworten auf Umfragen"
pollEnded: "Ende von Umfragen"
receiveFollowRequest: "Erhaltene Follow-Anfragen"
followRequestAccepted: "Akzeptierte Follow-Anfragen"
groupInvited: "Erhaltene Gruppeneinladungen"
@ -1624,10 +1633,10 @@ _deck:
columnMargin: "Spaltenabstand"
columnHeaderHeight: "Spaltenkopfhöhe"
addColumn: "Spalte hinzufügen"
swapLeft: "Nach links verschieben"
swapRight: "Nach rechts verschieben"
swapUp: "Nach oben verschieben"
swapDown: "Nach unten verschieben"
swapLeft: "Mit linker Spalte tauschen"
swapRight: "Mit rechter Spalte tauschen"
swapUp: "Mit oberer Spalte tauschen"
swapDown: "Mit unterer Spalte tauschen"
stackLeft: "Auf linke Spalte stapeln"
popRight: "Nach rechts vom Stapel nehmen"
profile: "Profil"

View file

@ -8,7 +8,7 @@ notifications: "Notifications"
username: "Username"
password: "Password"
forgotPassword: "Forgot password"
fetchingAsApObject: "Fetching from Fediverse..."
fetchingAsApObject: "Fetching from the Fediverse..."
ok: "OK"
gotIt: "Got it!"
cancel: "Cancel"
@ -32,9 +32,9 @@ uploading: "Uploading..."
save: "Save"
users: "Users"
addUser: "Add a user"
favorite: "Favorite"
favorite: "Add to favorites"
favorites: "Favorites"
unfavorite: "Unfavorite"
unfavorite: "Remove from favorites"
favorited: "Added to favorites."
alreadyFavorited: "Already added to favorites."
cantFavorite: "Couldn't add to favorites."
@ -43,7 +43,7 @@ unpin: "Unpin from profile"
copyContent: "Copy contents"
copyLink: "Copy link"
delete: "Delete"
deleteAndEdit: "Delete and Edit"
deleteAndEdit: "Delete and edit"
deleteAndEditConfirm: "Are you sure you want to delete this note and edit it? You will lose all reactions, renotes and replies to it."
addToList: "Add to list"
sendMessage: "Send a message"
@ -77,21 +77,21 @@ followsYou: "Follows you"
createList: "Create list"
manageLists: "Manage lists"
error: "Error"
somethingHappened: "An error occurred"
somethingHappened: "An error has occurred"
retry: "Retry"
pageLoadError: "Failed to load page."
pageLoadError: "An error occurred loading the page."
pageLoadErrorDescription: "This is normally caused by network errors or the browser's cache. Try clearing the cache and then try again after waiting a little while."
serverIsDead: "This server is not responding. Please wait for a while and try again."
youShouldUpgradeClient: "To view this page, please refresh to update your client."
enterListName: "Enter a list name"
enterListName: "Enter a name for the list"
privacy: "Privacy"
makeFollowManuallyApprove: "Follow requests require approval"
defaultNoteVisibility: "Default visibility"
follow: "Follow"
followRequest: "Request follow"
followRequest: "Send follow request"
followRequests: "Follow requests"
unfollow: "Unfollow"
followRequestPending: "Pending follow request"
followRequestPending: "Follow request pending"
enterEmoji: "Enter an emoji"
renote: "Renote"
unrenote: "Take back renote"
@ -107,12 +107,12 @@ sensitive: "NSFW"
add: "Add"
reaction: "Reactions"
reactionSetting: "Reactions to show in the reaction picker"
reactionSettingDescription2: "Drag to reorder, Click to delete, Press \"+\" to add"
reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
rememberNoteVisibility: "Remember note visibility settings"
attachCancel: "Remove attachment"
markAsSensitive: "Mark as NSFW"
unmarkAsSensitive: "Undo NSFW"
enterFileName: "Enter file name"
unmarkAsSensitive: "Unmark as NSFW"
enterFileName: "Enter filename"
mute: "Mute"
unmute: "Unmute"
block: "Block"
@ -168,8 +168,8 @@ latestRequestReceivedAt: "Last request received"
latestStatus: "Latest status"
storageUsage: "Storage usage"
charts: "Charts"
perHour: "per Hour"
perDay: "per Day"
perHour: "Per Hour"
perDay: "Per Day"
stopActivityDelivery: "Stop sending activities"
blockThisInstance: "Block this instance"
operations: "Operations"
@ -190,7 +190,7 @@ clearQueueConfirmText: "Any undelivered notes remaining in the queue will not be
clearCachedFiles: "Clear cache"
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
blockedInstances: "Blocked Instances"
blockedInstancesDescription: "List the hosts of the instances to be blocked separated by line breaks. Blocked instances will no longer be able to communicate with this instance."
blockedInstancesDescription: "List the hostnames of the instances that you want to block. Listed instances will no longer be able to communicate with this instance."
muteAndBlock: "Mutes and Blocks"
mutedUsers: "Muted users"
blockedUsers: "Blocked users"
@ -325,8 +325,6 @@ disablingTimelinesInfo: "Adminstrators and Moderators will always have access to
registration: "Register"
enableRegistration: "Enable new user registration"
invite: "Invite"
proxyRemoteFiles: "Proxy remote files"
proxyRemoteFilesDescription: "If enabled, remote files that are either not stored locally or were deleted due to exceeding the storage limit will be proxied, including generation of thumbnails. This does not affect the server's storage."
driveCapacityPerLocalAccount: "Drive capacity per local user"
driveCapacityPerRemoteAccount: "Drive capacity per remote user"
inMb: "In megabytes"
@ -336,9 +334,9 @@ backgroundImageUrl: "Background image URL"
basicInfo: "Basic info"
pinnedUsers: "Pinned users"
pinnedUsersDescription: "List usernames separated by line breaks to be pinned in the \"Explore\" tab."
pinnedPages: "Pinned pages"
pinnedPagesDescription: "Enter the paths of the pages you want to pin to the top page of this instance, separated by line breaks."
pinnedClipId: "ID of the pinned clip"
pinnedPages: "Pinned Pages"
pinnedPagesDescription: "Enter the paths of the Pages you want to pin to the top page of this instance, separated by line breaks."
pinnedClipId: "ID of the clip to pin"
pinnedNotes: "Pinned notes"
hcaptcha: "hCaptcha"
enableHcaptcha: "Enable hCaptcha"
@ -348,7 +346,7 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Enable reCAPTCHA"
recaptchaSiteKey: "Site key"
recaptchaSecretKey: "Secret key"
avoidMultiCaptchaConfirm: "Using multiple Captcha systems may cause interferences. Would you like to disable the other Captcha systems? You can leave multiple enabled by pressing cancel."
avoidMultiCaptchaConfirm: "Using multiple Captcha systems may cause interference between them. Would you like to disable the other Captcha systems currently active? If you would like them to stay enabled, press cancel."
antennas: "Antennas"
manageAntennas: "Manage Antennas"
name: "Name"
@ -369,13 +367,13 @@ silence: "Silence"
silenceConfirm: "Are you sure that you want to silence this user?"
unsilence: "Undo silencing"
unsilenceConfirm: "Are you sure that you want to undo the silencing of this user?"
popularUsers: "Trending users"
recentlyUpdatedUsers: "Users with recent activity"
popularUsers: "Popular users"
recentlyUpdatedUsers: "Recently active users"
recentlyRegisteredUsers: "Newly joined users"
recentlyDiscoveredUsers: "Newly discovered users"
exploreUsersCount: "There are {count} users"
exploreFediverse: "Explore the Fediverse"
popularTags: "Trending Tags"
popularTags: "Popular tags"
userList: "Lists"
about: "About"
aboutMisskey: "About Misskey"
@ -383,13 +381,13 @@ administrator: "Administrator"
token: "Token"
twoStepAuthentication: "Two-factor authentication"
moderator: "Moderator"
nUsersMentioned: "{n} users mentioned"
nUsersMentioned: "Mentioned by {n} users"
securityKey: "Security key"
securityKeyName: "Key name"
registerSecurityKey: "Register a security key"
lastUsed: "Last used"
unregister: "Unregister"
passwordLessLogin: "Set up password-less login"
passwordLessLogin: "Password-less login"
resetPassword: "Reset password"
newPasswordIs: "The new password is \"{password}\""
reduceUiAnimation: "Reduce UI animations"
@ -422,11 +420,10 @@ next: "Next"
retype: "Enter again"
noteOf: "Note by {user}"
inviteToGroup: "Invite to group"
maxNoteTextLength: "Character limit for notes"
quoteAttached: "Quoted"
quoteAttached: "Quote"
quoteQuestion: "Append as quote?"
noMessagesYet: "No messages yet"
newMessageExists: "You've got a new message"
newMessageExists: "There are new messages"
onlyOneFileCanBeAttached: "You can only attach one file to a message"
signinRequired: "Please sign in"
invitations: "Invites"
@ -438,7 +435,7 @@ usernameInvalidFormat: "You can use upper- and lowercase letters, numbers, and u
tooShort: "Too short"
tooLong: "Too long"
weakPassword: "Weak password"
normalPassword: "Normal password"
normalPassword: "Average password"
strongPassword: "Strong password"
passwordMatched: "Matches"
passwordNotMatched: "Does not match"
@ -466,7 +463,7 @@ existingAccount: "Existing account"
regenerate: "Regenerate"
fontSize: "Font size"
noFollowRequests: "You don't have any pending follow requests"
openImageInNewTab: "Open image in new tab"
openImageInNewTab: "Open images in new tab"
dashboard: "Dashboard"
local: "Local"
remote: "Remote"
@ -474,17 +471,17 @@ total: "Total"
weekOverWeekChanges: "Changes to last week"
dayOverDayChanges: "Changes to yesterday"
appearance: "Appearance"
clientSettings: "Client settings"
clientSettings: "Client Settings"
accountSettings: "Account Settings"
promotion: "Promoted"
promote: "Promote"
numberOfDays: "Number of days"
hideThisNote: "Hide this note"
showFeaturedNotesInTimeline: "Show Featured notes in Timelines"
showFeaturedNotesInTimeline: "Show featured notes in timelines"
objectStorage: "Object Storage"
useObjectStorage: "Use object storage"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "URL used as reference. Specify the URL of your CDN or Proxy if you are using either. S3: 'https://<bucket>.s3.amazonaws.com', GCS: 'https://storage.googleapis.com/<bucket>' etc."
objectStorageBaseUrlDesc: "The URL used as reference. Specify the URL of your CDN or Proxy if you are using either.\nFor S3 use 'https://<bucket>.s3.amazonaws.com' and for GCS or equivalent services use 'https://storage.googleapis.com/<bucket>', etc."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Please specify the bucket name used at your provider."
objectStoragePrefix: "Prefix"
@ -492,7 +489,7 @@ objectStoragePrefixDesc: "Files will be stored under directories with this prefi
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Leave this empty if you are using AWS S3, otherwise specify the endpoint as '<host>' or '<host>:<port>', depending on the service you are using."
objectStorageRegion: "Region"
objectStorageRegionDesc: "Specify a region like 'xx-east-1'. If your service does not distinct between regions, leave this blank or enter 'us-east-1'."
objectStorageRegionDesc: "Specify a region like 'xx-east-1'. If your service does not distinguish between regions, leave this blank or enter 'us-east-1'."
objectStorageUseSSL: "Use SSL"
objectStorageUseSSLDesc: "Turn this off if you are not going to use HTTPS for API connections"
objectStorageUseProxy: "Connect over Proxy"
@ -524,17 +521,17 @@ sort: "Sort"
ascendingOrder: "Ascending"
descendingOrder: "Descending"
scratchpad: "Scratchpad"
scratchpadDescription: "The Scratchpad provides an environment for AiScript experiments. You can write, execute, and check the results of it interacting with Misskey."
scratchpadDescription: "The Scratchpad provides an environment for AiScript experiments. You can write, execute, and check the results of it interacting with Misskey in it."
output: "Output"
script: "Script"
disablePagesScript: "Disable AiScript on Pages"
updateRemoteUser: "Update remote user information"
deleteAllFiles: "Delete All Files"
deleteAllFiles: "Delete all files"
deleteAllFilesConfirm: "Are you sure that you want to delete all files?"
removeAllFollowing: "Unfollow all followed users"
removeAllFollowingDescription: "Executing this unfollows all accounts from {host}. Please run this if the instance e.g. no longer exists."
userSuspended: "This user has been suspended."
userSilenced: "This user has been silenced."
userSilenced: "This user is being silenced."
yourAccountSuspendedTitle: "This account is suspended"
yourAccountSuspendedDescription: "This account has been suspended due to breaking the server's terms of services or similar. Contact the administrator if you would like to know a more detailed reason. Please do not create a new account."
menu: "Menu"
@ -547,7 +544,7 @@ addedRelays: "Added Relays"
serviceworkerInfo: "Must be enabled for push notifications."
deletedNote: "Deleted note"
invisibleNote: "Invisible note"
enableInfiniteScroll: "Enable infinite scrolling"
enableInfiniteScroll: "Automatically load more"
visibility: "Visiblility"
poll: "Poll"
useCw: "Hide content"
@ -585,7 +582,7 @@ enableEmail: "Enable email distribution"
emailConfigInfo: "Used to confirm your email during sign-up or if you forget your password"
email: "Email"
emailAddress: "Email address"
smtpConfig: "SMTP Server configuration"
smtpConfig: "SMTP Server Configuration"
smtpHost: "Host"
smtpPort: "Port"
smtpUser: "Username"
@ -595,7 +592,9 @@ smtpSecure: "Use implicit SSL/TLS for SMTP connections"
smtpSecureInfo: "Turn this off when using STARTTLS"
testEmail: "Test email delivery"
wordMute: "Word mute"
instanceMute: "Instance mutes"
regexpError: "Regular Expression error"
regexpErrorDescription: "An error occurred in the regular expression on line {line} of your {tab} word mutes:"
instanceMute: "Instance Mutes"
userSaysSomething: "{name} said something"
makeActive: "Activate"
display: "Display"
@ -608,14 +607,14 @@ database: "Database"
channel: "Channels"
create: "Create"
notificationSetting: "Notification settings"
notificationSettingDesc: "Select the type of notification to display"
useGlobalSetting: "Use global setting"
notificationSettingDesc: "Select the types of notification to display."
useGlobalSetting: "Use global settings"
useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made."
other: "Other"
regenerateLoginToken: "Regenerate login token"
regenerateLoginTokenDescription: "Regenerate the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
fileIdOrUrl: "File-ID or URL"
fileIdOrUrl: "File ID or URL"
behavior: "Behavior"
sample: "Sample"
abuseReports: "Reports"
@ -689,14 +688,14 @@ center: "Center"
wide: "Wide"
narrow: "Narrow"
reloadToApplySetting: "This setting will only apply after a page reload. Reload now?"
needReloadToApply: "This setting will only apply after a page reload."
needReloadToApply: "A reload is required for this to be reflected."
showTitlebar: "Show title bar"
clearCache: "Clear cache"
onlineUsersCount: "{n} users are online"
nUsers: "{n} Users"
nNotes: "{n} Notes"
sendErrorReports: "Send error reports"
sendErrorReportsDescription: "When turned on, detailed error information will be shared with Misskey when a problem occurs, helping to improve the quality of Misskey.\nThis will include information such the version of your OS, what browser you're using, your activity history, etc."
sendErrorReportsDescription: "When turned on, detailed error information will be shared with Misskey when a problem occurs, helping to improve the quality of Misskey.\nThis will include information such the version of your OS, what browser you're using, your activity in Misskey, etc."
myTheme: "My theme"
backgroundColor: "Background color"
accentColor: "Accent color"
@ -794,12 +793,12 @@ translatedFrom: "Translated from {x}"
accountDeletionInProgress: "Account deletion is currently in progress"
usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later."
aiChanMode: "Ai Mode"
keepCw: "Keep Content Warnings"
keepCw: "Keep content warnings"
pubSub: "Pub/Sub Accounts"
lastCommunication: "Last communication"
resolved: "Resolved"
unresolved: "Unresolved"
breakFollow: "Unfollow"
breakFollow: "Remove follower"
itsOn: "Enabled"
itsOff: "Disabled"
emailRequiredForSignup: "Require email address for sign-up"
@ -819,7 +818,7 @@ deleteAccountConfirm: "This will irreversibly delete your account. Proceed?"
incorrectPassword: "Incorrect password."
voteConfirm: "Confirm your vote for \"{choice}\"?"
hide: "Hide"
leaveGroup: "Leave Group"
leaveGroup: "Leave group"
leaveGroupConfirm: "Are you sure you want to leave \"{name}\"?"
useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile"
welcomeBackWithName: "Welcome back, {name}"
@ -831,6 +830,16 @@ auto: "Auto"
themeColor: "Theme Color"
size: "Size"
numberOfColumn: "Number of columns"
searchByGoogle: "Google"
instanceDefaultLightTheme: "Instance-wide default light theme"
instanceDefaultDarkTheme: "Instance-wide default dark theme"
instanceDefaultThemeDescription: "Enter the theme code in object format."
mutePeriod: "Mute duration"
indefinitely: "Permanently"
tenMinutes: "10 minutes"
oneHour: "One hour"
oneDay: "One day"
oneWeek: "One week"
_emailUnavailable:
used: "This email address is already being used"
format: "The format of this email address is invalid"
@ -843,10 +852,10 @@ _ffVisibility:
private: "Private"
_signup:
almostThere: "Almost there"
emailAddressInfo: "Please enter your email address."
emailAddressInfo: "Please enter your email address. It will not be made public."
emailSent: "A confirmation email has been sent to your email address ({email}). Please click the included link to complete account creation."
_accountDelete:
accountDelete: "Delete Account"
accountDelete: "Delete account"
mayTakeTime: "As account deletion is a resource-heavy process, it may take some time to complete depending on how much content you have created and how many files you have uploaded."
sendEmail: "Once account deletion has been completed, an email will be sent to the email address registered to this account."
requestAccountDelete: "Request account deletion"
@ -857,8 +866,8 @@ _ad:
reduceFrequencyOfThisAd: "Show this ad less"
_forgotPassword:
enterEmail: "Enter the email address you used to register. A link with which you can reset your password will then be sent to it."
ifNoEmail: "If you did not use an email during registration, please contact the administrator instead."
contactAdmin: "This instance does not support using email addresses, please contact the administrator to reset your password instead."
ifNoEmail: "If you did not use an email during registration, please contact the instance administrator instead."
contactAdmin: "This instance does not support using email addresses, please contact the instance administrator to reset your password instead."
_gallery:
my: "My Gallery"
liked: "Liked Posts"
@ -910,14 +919,14 @@ _mfm:
smallDescription: "Displays content small and thin."
center: "Center"
centerDescription: "Displays content centered."
inlineCode: "Code (In-line)"
inlineCode: "Code (Inline)"
inlineCodeDescription: "Displays inline syntax highlighting for (program) code."
blockCode: "Code (Block)"
blockCodeDescription: "Displays syntax highlighting for multi-line (program) code in a block."
inlineMath: "Math (In-line)"
inlineMath: "Math (Inline)"
inlineMathDescription: "Display math formulas (KaTeX) in-line"
blockMath: "Math (Block)"
blockMathDescription: "Display multi-line Math formulas (KaTeX) in a block"
blockMathDescription: "Display multi-line math formulas (KaTeX) in a block"
quote: "Quote"
quoteDescription: "Displays content as a quote."
emoji: "Custom Emoji"
@ -947,7 +956,7 @@ _mfm:
x4: "Unbelievably big"
x4Description: "Displays content even bigger than bigger than big."
blur: "Blur"
blurDescription: "Content can be blurred via this effect. It will be displayed clearly when hovered over."
blurDescription: "Blurs content. It will be displayed clearly when hovered over."
font: "Font"
fontDescription: "Sets the font to display content in."
rainbow: "Rainbow"
@ -1079,10 +1088,10 @@ _ago:
unknown: "Unknown"
future: "Future"
justNow: "Just now"
secondsAgo: "{n} seconds ago"
minutesAgo: "{n} minutes ago"
hoursAgo: "{n} hours ago"
daysAgo: "{n} days ago"
secondsAgo: "{n} second(s) ago"
minutesAgo: "{n} minute(s) ago"
hoursAgo: "{n} hour(s) ago"
daysAgo: "{n} day(s) ago"
weeksAgo: "{n} week(s) ago"
monthsAgo: "{n} month(s) ago"
yearsAgo: "{n} year(s) ago"
@ -1099,17 +1108,17 @@ _tutorial:
step2_1: "Let's finish setting up your profile before writing a note or following anyone."
step2_2: "Providing some information about who you are will make it easier for others to tell if they want to see your notes or follow you."
step3_1: "Finished setting up your profile?"
step3_2: "Then let's try posting a note next. You can do this by pressing the pencil icon on the top of the screen."
step3_2: "Then let's try posting a note next. You can do so by pressing the button with a pencil icon on the screen."
step3_3: "Fill in the modal and press the button on the top right to post."
step3_4: "Have nothing to say? Try \"just setting up my msky\"!"
step4_1: "Finished posting your first note?"
step4_2: "Hurray! Now your first note should be displayed on your timeline."
step5_1: "Now, let's try making your timeline more lively by following other people."
step5_2: "{featured} will show you trending notes in this instance. {explore} will let you find trending users. Try finding people you'd like to follow there!"
step5_2: "{featured} will show you popular notes in this instance. {explore} will let you find popular users. Try finding people you'd like to follow there!"
step5_3: "To follow other users, click on their icon and press the \"Follow\" button on their profile."
step5_4: "If the other user has a lock icon next to their name, it may take some time for that user to manually approve your follow request."
step6_1: "You should be able to see other users' notes on your timeline now."
step6_2: "You can also put \"reactions\" on other people's notes to quickly respond."
step6_2: "You can also put \"reactions\" on other people's notes to quickly respond to them."
step6_3: "To attach a \"reaction\", press the \"+\" mark on another user's note and choose an emoji you'd like to react with."
step7_1: "Congratulations! You have now finished Misskey's basic tutorial."
step7_2: "If you would like to learn more about Misskey, try the {help} section."
@ -1140,7 +1149,7 @@ _permissions:
"write:mutes": "Edit your list of muted users"
"write:notes": "Compose or delete notes"
"read:notifications": "View your notifications"
"write:notifications": "Work with your notifications"
"write:notifications": "Manage your notifications"
"read:reactions": "View your reactions"
"write:reactions": "Edit your reactions"
"write:votes": "Vote on a poll"
@ -1150,18 +1159,18 @@ _permissions:
"write:page-likes": "Edit your likes on pages"
"read:user-groups": "View your user groups"
"write:user-groups": "Edit or delete your user groups"
"read:channels": "Read your channels"
"write:channels": "Modify your channels"
"read:channels": "View your channels"
"write:channels": "Edit your channels"
"read:gallery": "View your gallery"
"write:gallery": "Edit your gallery"
"read:gallery-likes": "View list of liked gallery posts"
"write:gallery-likes": "Edit list of liked gallery posts"
"read:gallery-likes": "View your list of liked gallery posts"
"write:gallery-likes": "Edit your list of liked gallery posts"
_auth:
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
shareAccessAsk: "Are you sure you want to authorize this application to access your account?"
permissionAsk: "This application requests the following permissions"
pleaseGoBack: "Please go back to the application"
callback: "Returning back to the application"
callback: "Returning to the application"
denied: "Access denied"
_antennaSources:
all: "All notes"
@ -1189,7 +1198,7 @@ _widgets:
photos: "Photos"
digitalClock: "Digital clock"
federation: "Federation"
postForm: "Compose a note"
postForm: "Posting form"
slideshow: "Slideshow"
button: "Button"
onlineUsers: "Online users"
@ -1220,10 +1229,10 @@ _poll:
showResult: "View results"
voted: "Voted"
closed: "Ended"
remainingDays: "{d} days {h} hours remaining"
remainingHours: "{h} hours {m} minutes remaining"
remainingMinutes: "{m} minutes {s} seconds remaining"
remainingSeconds: "{s} seconds remaining"
remainingDays: "{d} day(s) {h} hour(s) remaining"
remainingHours: "{h} hour(s) {m} minute(s) remaining"
remainingMinutes: "{m} minute(s) {s} second(s) remaining"
remainingSeconds: "{s} second(s) remaining"
_visibility:
public: "Public"
publicDescription: "Your note will be visible for all users"
@ -1253,7 +1262,7 @@ _profile:
youCanIncludeHashtags: "You can also include hashtags in your bio."
metadata: "Additional Information"
metadataEdit: "Edit additional Information"
metadataDescription: "You can display up to four additional information fields in your profile."
metadataDescription: "Using these, you can display additional information fields in your profile."
metadataLabel: "Label"
metadataContent: "Content"
changeAvatar: "Change avatar"
@ -1269,29 +1278,29 @@ _exportOrImport:
_charts:
federation: "Federation"
apRequest: "Requests"
usersIncDec: "Difference in # of users"
usersTotal: "Total # of users"
usersIncDec: "Difference in the number of users"
usersTotal: "Total number of users"
activeUsers: "Active users"
notesIncDec: "Difference in # of notes"
localNotesIncDec: "Difference in # of local notes"
remoteNotesIncDec: "Difference in # of remote notes"
notesTotal: "Total # of notes"
filesIncDec: "Difference in # of files"
filesTotal: "Total # of files"
notesIncDec: "Difference in the number of notes"
localNotesIncDec: "Difference in the number of local notes"
remoteNotesIncDec: "Difference in the number of remote notes"
notesTotal: "Total number of notes"
filesIncDec: "Difference in the number of files"
filesTotal: "Total number of files"
storageUsageIncDec: "Difference in storage usage"
storageUsageTotal: "Total storage usage"
_instanceCharts:
requests: "Requests"
users: "Difference in # of users"
usersTotal: "Cumulative total # of users"
notes: "Difference in # of notes"
notesTotal: "Cumulative total # of notes"
ff: "Difference in # of followed users / followers "
ffTotal: "Cumulative total # of followed users / followers"
users: "Difference in the number of users"
usersTotal: "Cumulative number of users"
notes: "Difference in the number of notes"
notesTotal: "Cumulative number of notes"
ff: "Difference in the number of followed users / followers "
ffTotal: "Cumulative number of followed users / followers"
cacheSize: "Difference in cache size"
cacheSizeTotal: "Cumulative total cache size"
files: "Difference in # of files"
filesTotal: "Cumulative total # of files"
files: "Difference in the number of files"
filesTotal: "Cumulative number of files"
_timelines:
home: "Home"
local: "Local"
@ -1300,7 +1309,7 @@ _timelines:
_pages:
newPage: "Create a new Page"
editPage: "Edit this Page"
readPage: "Source view activated"
readPage: "Viewing this Page's source"
created: "Page successfully created"
updated: "Page successfully edited"
deleted: "Page successfully deleted"
@ -1315,7 +1324,7 @@ _pages:
unlike: "Remove like"
my: "My Pages"
liked: "Liked Pages"
featured: "Featured"
featured: "Popular"
inspector: "Inspector"
contents: "Contents"
content: "Page block"
@ -1346,10 +1355,10 @@ _pages:
if: "If"
_if:
variable: "Variable"
post: "Compose a note"
post: "Posting form"
_post:
text: "Content"
attachCanvasImage: "Post with canvas image"
attachCanvasImage: "Attach canvas image"
canvasId: "Canvas ID"
textInput: "Text input"
_textInput:
@ -1385,11 +1394,11 @@ _pages:
_counter:
name: "Variable name"
text: "Title"
inc: "Increase by"
inc: "Step"
_button:
text: "Title"
colored: "Colored"
action: "Operation when the button is pressed"
action: "Behavior when the button is pressed"
_action:
dialog: "Show a dialog"
_dialog:
@ -1431,11 +1440,11 @@ _pages:
strLen: "Text length"
_strLen:
arg1: "Text"
strPick: "Extract character"
strPick: "Extract string"
_strPick:
arg1: "Text"
arg2: "Character location"
strReplace: "Text replacement"
arg2: "String location"
strReplace: "Replacement string"
_strReplace:
arg1: "Text"
arg2: "Text to be replaced"
@ -1529,7 +1538,7 @@ _pages:
arg2: "Maximum value"
dailyRandomPick: "Randomly choose from a list (Changes once a day for each user)"
_dailyRandomPick:
arg1: "Lists"
arg1: "List"
seedRandom: "Random (with seed)"
_seedRandom:
arg1: "Seed"
@ -1603,6 +1612,7 @@ _notification:
youReceivedFollowRequest: "You've received a follow request"
yourFollowRequestAccepted: "Your follow request was accepted"
youWereInvitedToGroup: "You've been invited to a group"
pollEnded: "Poll results have become available"
_types:
all: "All"
follow: "New followers"
@ -1612,6 +1622,7 @@ _notification:
quote: "Quotes"
reaction: "Reactions"
pollVote: "Votes on polls"
pollEnded: "Polls ending"
receiveFollowRequest: "Received follow requests"
followRequestAccepted: "Accepted follow requests"
groupInvited: "Group invitations"
@ -1622,12 +1633,12 @@ _deck:
columnMargin: "Margin between columns"
columnHeaderHeight: "Column header height"
addColumn: "Add column"
swapLeft: "Swap left"
swapRight: "Swap right"
swapUp: "Swap with above"
swapDown: "Swap with below"
stackLeft: "Stack on left column"
popRight: "Pop to the right"
swapLeft: "Swap with the left column"
swapRight: "Swap with the right column"
swapUp: "Swap with the above column"
swapDown: "Swap with the below column"
stackLeft: "Stack with the left column"
popRight: "Pop column to the right"
profile: "Profile"
_columns:
main: "Main"

View file

@ -9,7 +9,7 @@ username: "Uzantnomo"
password: "Pasvorto"
forgotPassword: "Ĉu vi forgesis pasvorton?"
fetchingAsApObject: "Informpetado de la Fediverso…"
ok: "Bone"
ok: "Okej"
gotIt: "Kompreni"
cancel: "Nuligi"
enterUsername: "Entajpu uzantnomon"
@ -71,7 +71,7 @@ lists: "Listoj"
noLists: "Neniu listo"
note: "Noti"
notes: "Notoj"
following: "Sekvata"
following: "Sekvatoj"
followers: "Sekvantoj"
followsYou: "Sekvas vin"
createList: "Krei liston"
@ -224,7 +224,7 @@ resetAreYouSure: "Ĉu vi certas restarigi?"
saved: "Konservita"
messaging: "Retbabili"
upload: "Alŝuti"
keepOriginalUploading: "Konservi la originalan bildon"
keepOriginalUploading: "Konservi originalon"
fromDrive: "De la disko"
fromUrl: "De URL"
uploadFromUrl: "Alŝuti de URL"
@ -278,6 +278,7 @@ rename: "Alinomi"
avatar: "Bildsimbolo"
banner: "Standardo"
nsfw: "Enhavo ne estas deca por laborejo (NSFW)"
whenServerDisconnected: "Kiam vi malligiĝas de servilo"
disconnectedFromServer: "Malkonektita de servilo"
reload: "Reŝargi"
doNothing: "Ignori"
@ -396,7 +397,6 @@ next: "Sekve"
retype: "Retajpu"
noteOf: "Noto de {user}"
inviteToGroup: "Inviti al grupo"
maxNoteTextLength: "Limnombro de leteroj en noto"
quoteAttached: "Kun citaĵo"
quoteQuestion: "Ĉu vi volas aldoni citaĵon?"
noMessagesYet: "Ankoraŭ neniu mesaĝo"
@ -420,7 +420,7 @@ signinWith: "Saluti kun {x}"
signinFailed: "Fuŝiĝis ensaluti. Bonvolu kontroli uzantnomon kaj pasvorton."
or: "Aŭ"
language: "Lingvo"
uiLanguage: "Lingvo de fasado"
uiLanguage: "Lingvo de la fasado"
groupInvited: "Invitita al grupo"
aboutX: "Pri {x}"
useOsNativeEmojis: "Uzi la emoĵiojn implicitan de la operaciumo"
@ -648,7 +648,7 @@ high: "Alta"
middle: "Meza"
low: "Malalta"
emailNotConfiguredWarning: "Vi ne agordis retpoŝtadreso."
customCss: "Personecigita CSS"
customCss: "Propra CSS"
global: "Malloka"
squareAvatars: "Montri bildsimbolon kiel kvadrata"
sent: "Sendi"
@ -683,14 +683,20 @@ hide: "Kaŝi"
leaveGroup: "Eliĝi el la grupo"
leaveGroupConfirm: "Ĉu vi certas ke vi volas eliĝi el la grupo {name}?"
welcomeBackWithName: "Bonrevenon, {name}!"
clickToFinishEmailVerification: "Volu klaki [{ok}] por fini la konfirmon de vian retadreson"
clickToFinishEmailVerification: "Volu klaki [{ok}] por fini konfirmon de via retadreso."
smartphone: "Saĝtelefono"
tablet: "Platkomputilo"
auto: "Aŭtomate"
searchByGoogle: "Serĉi en Google-Serĉo"
tenMinutes: "10 minutoj"
oneHour: "1 horo"
oneDay: "1 tago"
oneWeek: "1 semajno"
_emailUnavailable:
used: "La retpoŝto jam estas uzita."
format: "Nevalida formato."
disposable: "Dumtempa retpoŝto ne estas uzebla."
mx: "Ĉi retpoŝto-servilo ne estas uzebla."
smtp: "Tiu retpoŝta servilo ne respondas"
_ffVisibility:
public: "Publika"
@ -770,9 +776,9 @@ _channel:
usersCount: "{n} partoprenantoj"
notesCount: "{n} notoj"
_menuDisplay:
sideFull: "Flanko"
sideIcon: "Flanko (bildsimbolo)"
top: "Supro"
sideFull: "Sur la flanko"
sideIcon: "Sur la flanko (bildsimbolo)"
top: "Sur la supro"
hide: "Kaŝi"
_wordMute:
muteWords: "Silentigitaj vortoj"
@ -920,7 +926,6 @@ _postForm:
c: "Kio estas sur via penso?"
d: "Kion vi volas diri?"
e: "Komencu skribi tie"
f: "Atendanta de vi skribon…"
_profile:
name: "Nomo"
username: "Uzantnomo"

View file

@ -321,8 +321,6 @@ disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conven
registration: "Registro"
enableRegistration: "Permitir nuevos registros"
invite: "Invitar"
proxyRemoteFiles: "Hacer proxy de archivos remotos"
proxyRemoteFilesDescription: "Si activa esta configuración, los archivos remotos no almacenados o borrados por exceso de capacidad serán mostrados via proxy local y generarán una miniatura. Eso no afectará el almacenamiento del servidor."
driveCapacityPerLocalAccount: "Capacidad del drive por usuario local"
driveCapacityPerRemoteAccount: "Capacidad del drive por usuario remoto"
inMb: "En megabytes"
@ -418,7 +416,6 @@ next: "Siguiente"
retype: "Intentar de nuevo"
noteOf: "Notas de {user}"
inviteToGroup: "Invitar al grupo"
maxNoteTextLength: "Límite de caracteres en una nota"
quoteAttached: "Cita añadida"
quoteQuestion: "¿Quiere añadir una cita?"
noMessagesYet: "Aún no hay chat"
@ -765,6 +762,8 @@ muteThread: "Ocultar hilo"
unmuteThread: "Mostrar hilo"
ffVisibility: "Visibilidad de seguidores y seguidos"
hide: "Ocultar"
searchByGoogle: "Buscar"
indefinitely: "Sin límite de tiempo"
_ffVisibility:
public: "Publicar"
_accountDelete:

View file

@ -323,8 +323,6 @@ disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur
registration: "Sinscrire"
enableRegistration: "Autoriser les nouvelles inscriptions"
invite: "Inviter"
proxyRemoteFiles: "Utiliser les fichiers distants comme proxy"
proxyRemoteFilesDescription: "Si vous activez ce paramètre, les fichiers distants non stockés ou supprimés en raison d'une capacité excédentaire seront affichés via un proxy local et généreront une miniature. Cela n'affectera pas le stockage du serveur."
driveCapacityPerLocalAccount: "Volume du Drive par utilisateur local"
driveCapacityPerRemoteAccount: "Volume du Drive par utilisateur distant"
inMb: "en mégaoctets"
@ -420,7 +418,6 @@ next: "Suivant"
retype: "Confirmation"
noteOf: "Notes de {user}"
inviteToGroup: "Inviter dans un groupe"
maxNoteTextLength: "Limite du nombre de caractères pour les notes"
quoteAttached: "Avec citation"
quoteQuestion: "Souhaitez-vous ajouter une citation ?"
noMessagesYet: "Pas encore de discussion"
@ -592,6 +589,7 @@ smtpSecure: "Utiliser SSL/TLS implicitement dans les connexions SMTP"
smtpSecureInfo: "Désactiver cette option lorsque STARTTLS est utilisé"
testEmail: "Tester la distribution de courriel"
wordMute: "Filtre de mots"
regexpError: "Erreur dexpression régulière"
instanceMute: "Instance en sourdine"
userSaysSomething: "{name} a dit quelque chose"
makeActive: "Activer"
@ -620,6 +618,7 @@ reportAbuse: "Signaler"
reportAbuseOf: "Signaler {name}"
fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien."
abuseReported: "Le rapport est envoyé. Merci."
reporter: "Signalé par"
reporteeOrigin: "Origine du signalement"
reporterOrigin: "Signalé par"
forwardReport: "Transférer le signalement à linstance distante"
@ -818,6 +817,22 @@ leaveGroup: "Quitter le groupe"
leaveGroupConfirm: "Êtes vous sûr de vouloir quitter \"{name}\" ?"
welcomeBackWithName: "Heureux de vous revoir, {name}"
clickToFinishEmailVerification: "Veuillez cliquer sur [{ok}] afin de compléter la vérification par courriel."
overridedDeviceKind: "Type dappareil"
smartphone: "Smartphone"
tablet: "Tablette"
auto: "Automatique"
themeColor: "Couleur du thème"
size: "Taille"
numberOfColumn: "Nombre de colonnes"
searchByGoogle: "Google"
instanceDefaultLightTheme: "Thème clair par défaut sur toute linstance"
instanceDefaultDarkTheme: "Thème sombre par défaut sur toute linstance"
mutePeriod: "Durée de mise en sourdine"
indefinitely: "Illimité"
tenMinutes: "10 minutes"
oneHour: "1 heure"
oneDay: "1 jour"
oneWeek: "1 semaine"
_emailUnavailable:
used: "Non disponible"
format: "Le format de cette adresse de courriel est invalide"

1
locales/hr-HR.yml Normal file
View file

@ -0,0 +1 @@
---

View file

@ -325,8 +325,6 @@ disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua
registration: "Pendaftaran"
enableRegistration: "Nyalakan pendaftaran pengguna baru"
invite: "Undang"
proxyRemoteFiles: "Proksi berkas remote"
proxyRemoteFilesDescription: "Jika diaktifkan, berkas luar yang (1) tidak disimpan secara lokal atau (2) terhapus dari melewati batas penyimpanan akan diproksi secara lokal (dengan thumbnail). Ini tidak akan mempengaruhi server penyimpanan."
driveCapacityPerLocalAccount: "Kapasitas drive per pengguna lokal"
driveCapacityPerRemoteAccount: "Kapasitas drive per pengguna remote"
inMb: "dalam Megabytes"
@ -422,7 +420,6 @@ next: "Selanjutnya"
retype: "Masukkan ulang"
noteOf: "Catatan milik {user}"
inviteToGroup: "Undang ke grup"
maxNoteTextLength: "Batas karakter catatan"
quoteAttached: "Dikutip"
quoteQuestion: "Apakah kamu ingin menambahkan kutipan?"
noMessagesYet: "Tidak ada pesan"
@ -828,6 +825,8 @@ overridedDeviceKind: "Tipe perangkat"
smartphone: "Ponsel"
tablet: "Tablet"
auto: "Otomatis"
searchByGoogle: "Penelusuran"
indefinitely: "Selamanya"
_emailUnavailable:
used: "Alamat surel ini telah digunakan"
format: "Format tidak valid."

View file

@ -321,8 +321,6 @@ disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori
registration: "Iscriviti"
enableRegistration: "Permettere nuove registrazioni"
invite: "Invita"
proxyRemoteFiles: "Usare file remoti come proxy"
proxyRemoteFilesDescription: "Attivando questa opzione i file remoti non salvati o cancellati perché eccedenti il limite di archiviazione verranno inoltrati tramite proxy, inclusa la generazione di anteprime. Non ha effetto sullo spazio di archiviazione del server."
driveCapacityPerLocalAccount: "Volume del Drive per utente locale"
driveCapacityPerRemoteAccount: "Volume del Drive per utente remoto"
inMb: "in Megabytes"
@ -418,7 +416,6 @@ next: "Avanti"
retype: "Conferma"
noteOf: "Note di {user}"
inviteToGroup: "Invitare al gruppo"
maxNoteTextLength: "Lunghezza massima delle note"
quoteAttached: "Citazione allegata"
quoteQuestion: "Vuoi aggiungere una citazione?"
noMessagesYet: "Ancora nessuna chat"
@ -805,6 +802,8 @@ leaveGroupConfirm: "Uscire da「{name}」?"
useDrawerReactionPickerForMobile: "Mostra sul drawer da dispositivo mobile"
welcomeBackWithName: "Bentornato/a, {name}"
clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email."
searchByGoogle: "Cerca"
indefinitely: "Non scade"
_emailUnavailable:
used: "Email già in uso"
format: "Formato email non valido"

View file

@ -830,10 +830,18 @@ auto: "自動"
themeColor: "テーマカラー"
size: "サイズ"
numberOfColumn: "列の数"
searchByGoogle: "ググる"
searchByGoogle: "検索"
instanceDefaultLightTheme: "インスタンスデフォルトのライトテーマ"
instanceDefaultDarkTheme: "インスタンスデフォルトのダークテーマ"
instanceDefaultThemeDescription: "オブジェクト形式のテーマコードを記入します。"
mutePeriod: "ミュートする期限"
indefinitely: "無期限"
tenMinutes: "10分"
oneHour: "1時間"
oneDay: "1日"
oneWeek: "1週間"
reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
_emailUnavailable:
used: "既に使用されています"
@ -1661,6 +1669,7 @@ _notification:
youReceivedFollowRequest: "フォローリクエストが来ました"
yourFollowRequestAccepted: "フォローリクエストが承認されました"
youWereInvitedToGroup: "グループに招待されました"
pollEnded: "アンケートの結果が出ました"
_types:
all: "すべて"
@ -1671,6 +1680,7 @@ _notification:
quote: "引用"
reaction: "リアクション"
pollVote: "アンケートに投票された"
pollEnded: "アンケートが終了"
receiveFollowRequest: "フォロー申請を受け取った"
followRequestAccepted: "フォローが受理された"
groupInvited: "グループに招待された"

View file

@ -323,8 +323,6 @@ disablingTimelinesInfo: "ここらへんのタイムラインを使えんよう
registration: "登録"
enableRegistration: "一見さんでも誰でもいらっしゃ~い"
invite: "来てや"
proxyRemoteFiles: "リモートのファイルをプロキシする"
proxyRemoteFilesDescription: "この設定を有効にしたら、保存してなかったり容量が足らんくて消されたリモートファイルをローカルでプロキシして、サムネイルを作るようになるで。サーバーの容量には関係ないで。"
driveCapacityPerLocalAccount: "ローカルユーザーひとりあたりのドライブ容量"
driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのドライブ容量"
inMb: "メガバイト単位"
@ -417,7 +415,6 @@ next: "次"
retype: "もっかい入力"
noteOf: "{user}のノート"
inviteToGroup: "グループに招く"
maxNoteTextLength: "ノートの文字数制限"
quoteAttached: "引用付いとるで"
quoteQuestion: "引用として添付してもええか?"
noMessagesYet: "まだチャットはあらへんで"
@ -658,6 +655,8 @@ global: "グローバル"
sent: "送信"
hashtags: "ハッシュタグ"
hide: "隠す"
searchByGoogle: "探す"
indefinitely: "無期限"
_ad:
back: "戻る"
_gallery:

View file

@ -55,6 +55,7 @@ accountInfo: "Talɣut n umiḍan"
emailNotification: "Ilɣa imayl"
selectAccount: "Fren amiḍan"
accounts: "Imiḍan"
searchByGoogle: "Nadi"
_email:
_follow:
title: "Yeṭṭafaṛ-ik·em-id"

View file

@ -59,6 +59,7 @@ remove: "ಅಳಿಸು"
smtpUser: "ಬಳಕೆಹೆಸರು"
smtpPass: "ಗುಪ್ತಪದ"
user: "ಬಳಕೆದಾರ"
searchByGoogle: "ಹುಡುಕು"
_email:
_follow:
title: "ಹಿಂಬಾಲಿಸಿದರು"

View file

@ -325,8 +325,6 @@ disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리
registration: "등록"
enableRegistration: "신규 회원가입을 활성화"
invite: "초대"
proxyRemoteFiles: "리모트 파일 프록시"
proxyRemoteFilesDescription: "이 설정을 활성화할 경우, 저장되지 않았거나 저장용량 초과로 삭제된 리모트 파일을 로컬에서 프록시하여 썸네일을 생성하게 됩니다. 서버의 스토리지에는 영향을 주지 않습니다."
driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량"
inMb: "메가바이트 단위"
@ -422,7 +420,6 @@ next: "다음"
retype: "다시 입력"
noteOf: "{user}의 노트"
inviteToGroup: "그룹에 초대하기"
maxNoteTextLength: "노트의 문자 수 제한"
quoteAttached: "인용함"
quoteQuestion: "인용해서 작성하시겠습니까?"
noMessagesYet: "아직 대화가 없습니다"
@ -828,6 +825,8 @@ overridedDeviceKind: "장치 유형"
smartphone: "스마트폰"
tablet: "태블릿"
auto: "자동"
searchByGoogle: "검색"
indefinitely: "무기한"
_emailUnavailable:
used: "이 메일 주소는 사용중입니다"
format: "형식이 올바르지 않습니다"

View file

@ -256,6 +256,7 @@ user: "Gebruikers"
muteThread: "Discussies dempen "
unmuteThread: "Dempen van discussie ongedaan maken"
hide: "Verbergen"
searchByGoogle: "Zoeken"
_email:
_follow:
title: "volgde jou"

View file

@ -320,8 +320,6 @@ disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dost
registration: "Zarejestruj się"
enableRegistration: "Włącz rejestrację nowych użytkowników"
invite: "Zaproś"
proxyRemoteFiles: "Przekierowuj pliki obcych instancji przez proxy"
proxyRemoteFilesDescription: "Gdy ta opcja jest włączona, zdalne pliki które nie są przechowywane lokalnie, lub zostały usunięte z powodu przekroczenia limitu miejsca będą kierowane przez proxy, razem z generowaniem miniatur. Nie ma to żadnego wpływu na przestrzeń dyskową serwera."
driveCapacityPerLocalAccount: "Powierzchnia dyskowa na lokalnego użytkownika"
driveCapacityPerRemoteAccount: "Powierzchnia dyskowa na zdalnego użytkownika"
inMb: "W megabajtach"
@ -417,7 +415,6 @@ next: "Dalej"
retype: "Wprowadź ponownie"
noteOf: "Wpisy {user}"
inviteToGroup: "Zaproś do grupy"
maxNoteTextLength: "Limit znaków dla wpisów"
quoteAttached: "Zacytowano"
quoteQuestion: "Czy na pewno chcesz umieścić cytat?"
noMessagesYet: "Nie napisano jeszcze wiadomości"
@ -761,6 +758,8 @@ received: "Otrzymane"
hashtags: "Hashtag"
pubSub: "Konta Pub/Sub"
hide: "Ukryj"
searchByGoogle: "Szukaj"
indefinitely: "Nigdy"
_ffVisibility:
public: "Publikuj"
_ad:

View file

@ -61,6 +61,7 @@ pinnedNotes: "Post fixado"
smtpUser: "Nome de usuário"
smtpPass: "Senha"
user: "Usuários"
searchByGoogle: "Pesquisar"
_email:
_follow:
title: "Você tem um novo seguidor"

556
locales/ro-RO.yml Normal file
View file

@ -0,0 +1,556 @@
---
_lang_: "Română"
headlineMisskey: "O rețea conectată prin note"
introMisskey: "Bine ai venit! Misskey este un serviciu de microblogging open source și decentralizat.\nCreează \"note\" cu care să îți poți împărți gândurile cu oricine din jurul tău. 📡\nCu \"reacții\" îți poți expirma rapid părerea despre notele oricui. 👍\nHai să explorăm o lume nouă! 🚀"
monthAndDay: "{day}/{month}"
search: "Caută"
notifications: "Notificări"
username: "Nume de utilizator"
password: "Parolă"
forgotPassword: "Am uitat parola"
fetchingAsApObject: "Se aduce din Fediverse..."
ok: "OK"
gotIt: "Am înțeles!"
cancel: "Anulează"
enterUsername: "Introdu numele de utilizator"
renotedBy: "Re-notat de {user}"
noNotes: "Nicio notă"
noNotifications: "Nicio notificare"
instance: "Instanță"
settings: "Setări"
basicSettings: "Setări generale"
otherSettings: "Alte Setări"
openInWindow: "Deschide într-o fereastră"
profile: "Profil"
timeline: "Cronologie"
noAccountDescription: "Acest utilizator încă nu a scris un bio."
login: "Autentifică-te"
loggingIn: "Se autentifică"
logout: "Deconectează-te"
signup: "Înregistrează-te"
uploading: "Se încarcă"
save: "Salvează"
users: "Utilizatori"
addUser: "Adăugă utilizator"
favorite: "Adaugă la favorite"
favorites: "Favorite"
unfavorite: "Elimină din favorite"
favorited: "Adăugat la favorite."
alreadyFavorited: "Deja adăugat la favorite."
cantFavorite: "Nu se poate adăuga la favorite."
pin: "Fixează pe profil"
unpin: "Anulati fixare"
copyContent: "Copiază conținutul"
copyLink: "Copiază link-ul"
delete: "Şterge"
deleteAndEdit: "Șterge și editează"
deleteAndEditConfirm: "Ești sigur că vrei să ștergi această notă și să o editezi? Vei pierde reacțiile, re-notele și răspunsurile acesteia."
addToList: "Adaugă în listă"
sendMessage: "Trimite un mesaj"
copyUsername: "Copiază numele de utilizator"
searchUser: "Caută un utilizator"
reply: "Răspunde"
loadMore: "Incarcă mai mult"
showMore: "Arată mai mult"
youGotNewFollower: "te-a urmărit"
receiveFollowRequest: "Cerere de urmărire primită"
followRequestAccepted: "Cerere de urmărire acceptată"
mention: "Mențiune"
mentions: "Mențiuni"
directNotes: "Note directe"
importAndExport: "Importă / Exportă"
import: "Importă"
export: "Exportă"
files: "Fișiere"
download: "Descarcă"
driveFileDeleteConfirm: "Ești sigur ca vrei să ștergi fișierul \"{name}\"? Notele atașate fișierului vor fi șterse și ele."
unfollowConfirm: "Ești sigur ca vrei să nu mai urmărești pe {name}?"
exportRequested: "Ai cerut un export. S-ar putea să ia un pic. Va fi adăugat in Drive-ul tău odată completat."
importRequested: "Ai cerut un import. S-ar putea să ia un pic."
lists: "Liste"
noLists: "Nu ai nici o listă"
note: "Notă"
notes: "Note"
following: "Urmărești"
followers: "Urmăritori"
followsYou: "Te urmărește"
createList: "Creează listă"
manageLists: "Gestionează listele"
error: "Eroare"
somethingHappened: "A survenit o eroare"
retry: "Reîncearcă"
pageLoadError: "A apărut o eroare la încărcarea paginii."
pageLoadErrorDescription: "De obicei asta este cauzat de o eroare de rețea sau cache-ul browser-ului. Încearcă să cureți cache-ul și apoi să încerci din nou puțin mai târziu."
serverIsDead: "Serverul nu răspunde. Te rugăm să aștepți o perioadă și să încerci din nou."
youShouldUpgradeClient: "Pentru a vedea această pagină, te rugăm să îți actualizezi clientul."
enterListName: "Introdu un nume pentru listă"
privacy: "Confidenţialitate"
makeFollowManuallyApprove: "Fă cererile de urmărire să necesite aprobare"
defaultNoteVisibility: "Vizibilitate implicită"
follow: "Urmărești"
followRequest: "Trimite cerere de urmărire"
followRequests: "Cereri de urmărire"
unfollow: "Nu mai urmări"
followRequestPending: "Cerere de urmărire în așteptare"
enterEmoji: "Introdu un emoji"
renote: "Re-notează"
unrenote: "Ia înapoi re-nota"
renoted: "Re-notat."
cantRenote: "Această postare nu poate fi re-notată."
cantReRenote: "O re-notă nu poate fi re-notată."
quote: "Citează"
pinnedNote: "Notă fixată"
pinned: "Fixat pe profil"
you: "Tu"
clickToShow: "Click pentru a afișa"
sensitive: "NSFW"
add: "Adaugă"
reaction: "Reacție"
reactionSetting: "Reacții care să apară in selectorul de reacții"
reactionSettingDescription2: "Trage pentru a rearanja, apasă pe \"+\" pentru a adăuga."
rememberNoteVisibility: "Amintește setarea de vizibilitate a notelor"
attachCancel: "Înlătură atașament"
markAsSensitive: "Marchează ca NSFW"
unmarkAsSensitive: "Demarchează ca NSFW"
enterFileName: "Introduceţi numele fişierului"
mute: "Amuțește"
unmute: "Înlătură amuțirea"
block: "Blochează"
unblock: "Deblochează"
suspend: "Suspendă"
unsuspend: "Anulează suspendare"
blockConfirm: "Ești sigur că vrei să blochezi acest cont?"
unblockConfirm: "Ești sigur ca vrei să deblochezi acest cont?"
suspendConfirm: "Ești sigur ca vrei să suspendezi acest cont?"
unsuspendConfirm: "Ești sigur ca vrei să nu mai suspendezi acest cont?"
selectList: "Selectează o listă"
selectAntenna: "Selectează o antenă"
selectWidget: "Selectați un widget"
editWidgets: "Editează widget-urile"
editWidgetsExit: "Terminat"
customEmojis: "Emoji personalizat"
emoji: "Emoji"
emojis: "Emoji-uri"
emojiName: "Numele emoji-ului"
emojiUrl: "URL-ul emoji-ului"
addEmoji: "Adaugă un emoji"
settingGuide: "Setări recomandate"
cacheRemoteFiles: "Ține fișierele externe in cache"
cacheRemoteFilesDescription: "Când această setare este dezactivată, fișierele externe sunt încărcate direct din instanța externă. Dezactivarea va scădea utilizarea spațiului de stocare, dar va crește traficul, deoarece thumbnail-urile nu vor fi generate."
flagAsBot: "Marchează acest cont ca bot"
flagAsBotDescription: "Activează această opțiune dacă acest cont este controlat de un program. Daca e activată, aceasta va juca rolul unui indicator pentru dezvoltatori pentru a preveni interacțiunea în lanțuri infinite cu ceilalți boți și ajustează sistemele interne al Misskey pentru a trata acest cont drept un bot."
flagAsCat: "Marchează acest cont ca pisică"
flagAsCatDescription: "Activează această opțiune dacă acest cont este o pisică."
flagShowTimelineReplies: "Arată răspunsurile în cronologie"
flagShowTimelineRepliesDescription: "Dacă e activată vor fi arătate în cronologie răspunsurile utilizatorilor către alte notele altor utilizatori."
autoAcceptFollowed: "Aprobă automat cererile de urmărire de la utilizatorii pe care îi urmărești"
addAccount: "Adaugă un cont"
loginFailed: "Autentificare eșuată"
showOnRemote: "Vezi mai multe pe instanța externă"
general: "General"
wallpaper: "Imagine de fundal"
setWallpaper: "Setați imaginea de fundal"
removeWallpaper: "Șterge imagine de fundal"
searchWith: "Caută: {q}"
youHaveNoLists: "Nu ai nici o listă"
followConfirm: "Ești sigur ca vrei să urmărești pe {name}?"
proxyAccount: "Cont proxy"
proxyAccountDescription: "Un cont proxy este un cont care se comportă ca un urmăritor extern pentru utilizatorii puși sub anumite condiții. De exemplu, când un cineva adaugă un utilizator extern intr-o listă, activitatea utilizatorului extern nu va fi adusă în instanță daca nici un utilizator local nu urmărește acel utilizator, așa că în schimb contul proxy îl va urmări."
host: "Gazdă"
selectUser: "Selectează un utilizator"
recipient: "Destinatar"
annotation: "Adnotări"
federation: "Federație"
instances: "Instanțe"
registeredAt: "Înregistrat în"
latestRequestSentAt: "Ultima cerere trimisă"
latestRequestReceivedAt: "Ultima cerere primită"
latestStatus: "Ultimul status"
storageUsage: "Utilizare stocare"
charts: "Diagrame"
perHour: "Pe oră"
perDay: "Pe zi"
stopActivityDelivery: "Nu mai trimite activități"
blockThisInstance: "Blochează această instanță"
operations: "Operațiuni"
software: "Software"
version: "Versiune"
metadata: "Metadata"
withNFiles: "{n} fișier(e)"
monitor: "Monitor"
jobQueue: "coada de job-uri"
cpuAndMemory: "CPU și memorie"
network: "Rețea"
disk: "Disk"
instanceInfo: "Informații despre instanță"
statistics: "Statistici"
clearQueue: "Șterge coada"
clearQueueConfirmTitle: "Ești sigur că vrei să cureți coada?"
clearQueueConfirmText: "Orice notă rămasă în coadă nu va fi federată. De obicei această operație nu este necesară."
clearCachedFiles: "Golește cache-ul"
clearCachedFilesConfirm: "Ești sigur că vrei să ștergi toate fișierele externe din cache?"
blockedInstances: "Instanțe blocate"
blockedInstancesDescription: "Scrie hostname-urile instanțelor pe care dorești să le blochezi. Instanțele listate nu vor mai putea să comunice cu această instanță."
muteAndBlock: "Amuțiri și Blocări"
mutedUsers: "Utilizatori amuțiți"
blockedUsers: "Utilizatori blocați"
noUsers: "Niciun utilizator"
editProfile: "Editează profilul"
noteDeleteConfirm: "Ești sigur că vrei să ștergi această notă?"
pinLimitExceeded: "Nu poți mai fixa mai multe note"
intro: "Misskey s-a instalat! Te rog crează un utilizator admin."
done: "Gata"
processing: "Se procesează"
preview: "Previzualizare"
default: "Prestabilit"
noCustomEmojis: "Nu e niciun emoji"
noJobs: "Nu e niciun job"
federating: "Federație"
blocked: "Blocat"
suspended: "Suspendat"
all: "Tot"
subscribing: "Abonare"
publishing: "Publicare"
notResponding: "Nu răspunde"
instanceFollowing: "Urmărind în instanță"
instanceFollowers: "Urmăritori ai instanței"
instanceUsers: "Utilizatori ai acestei instanțe"
changePassword: "Schimbă parolă"
security: "Securitate"
retypedNotMatch: "Intrările nu corespund"
currentPassword: "Parola curentă"
newPassword: "Parola nouă"
newPasswordRetype: "Rescrie parola nouă"
attachFile: "Atașează fișiere"
more: "Mai mult!"
featured: "Evidențiat"
usernameOrUserId: "Nume sau ID de utilizator"
noSuchUser: "Utilizatorul nu a fost găsit"
lookup: "Privire"
announcements: "Anunțuri"
imageUrl: "URL-ul imaginii"
remove: "Şterge"
removed: "Șterș cu succes"
removeAreYouSure: "Ești sigur că vrei să înlături {x}?"
deleteAreYouSure: "Ești sigur că vrei să ștergi {x}?"
resetAreYouSure: "Sigur vrei să resetezi?"
saved: "Salvat"
messaging: "Chat"
upload: "Încarcă"
keepOriginalUploading: "Păstrează imaginea originală"
keepOriginalUploadingDescription: "Salvează imaginea originala încărcată fără modificări. Dacă e oprită, o versiune pentru afișarea pe web va fi generată la încărcare."
fromDrive: "Din Drive"
fromUrl: "Din URL"
uploadFromUrl: "Încarcă dintr-un URL"
uploadFromUrlDescription: "URL-ul fișierului pe care dorești să îl încarci"
uploadFromUrlRequested: "Încărcare solicitată"
uploadFromUrlMayTakeTime: "S-ar putea să ia puțin până se finalizează încărcarea."
explore: "Explorează"
messageRead: "Citit"
noMoreHistory: "Nu există mai mult istoric"
startMessaging: "Începe un chat nou"
nUsersRead: "citit de {n}"
agreeTo: "Sunt de acord cu {0}"
tos: "Termenii de utilizare"
start: "Să începem"
home: "Acasă"
remoteUserCaution: "Deoarece acest utilizator este dintr-o instanță externă, informația afișată poate fi incompletă."
activity: "Activitate"
images: "Imagini"
birthday: "Zi de naștere"
yearsOld: "{age} ani"
registeredDate: "Data înregistrării"
location: "Locație"
theme: "Teme"
themeForLightMode: "Temă folosită pentru Modul Luminat"
themeForDarkMode: "Temă folosită pentru Modul Întunecat"
light: "Luminos"
dark: "Întunecat"
lightThemes: "Teme luminoase"
darkThemes: "Teme întunecate"
syncDeviceDarkMode: "Sincronizează Modul Întunecat cu setările dispozitivului"
drive: "Drive"
fileName: "Nume fișier"
selectFile: "Alege un fisier"
selectFiles: "Alege fișiere"
selectFolder: "Selectează un folder"
selectFolders: "Selectează folderele"
renameFile: "Redenumește fișier"
folderName: "Nume folder"
createFolder: "Crează folder"
renameFolder: "Redenumește acest folder"
deleteFolder: "Șterge acest folder"
addFile: "Adăugați un fișier"
emptyDrive: "Drive-ul tău e gol"
emptyFolder: "Folder-ul acesta este gol"
unableToDelete: "Nu se poate șterge"
inputNewFileName: "Introdu un nou nume de fișier"
inputNewDescription: "Introdu o descriere nouă"
inputNewFolderName: "Introdu un nume de folder nou"
circularReferenceFolder: "Destinația folderului este un subfolder al folderului pe care dorești să îl muți."
hasChildFilesOrFolders: "Acest folder nu este gol, așa că nu poate fi șters."
copyUrl: "Copiază URL"
rename: "Redenumește"
avatar: "Avatar"
banner: "Banner"
nsfw: "NSFW"
whenServerDisconnected: "Când pierzi conexiunea cu serverul"
disconnectedFromServer: "Conecțiunea cu serverul a fost pierdută"
reload: "Reîncarcă"
doNothing: "Ignoră"
reloadConfirm: "Ai dori să reîmprospătezi cronologia?"
watch: "Vezi"
unwatch: "Oprește-te din văzut"
accept: "Acceptă"
reject: "Respinge"
normal: "Normal"
instanceName: "Numele instanței"
instanceDescription: "Descrierea instanței"
maintainerName: "Administrator"
maintainerEmail: "Email-ul administratorului"
tosUrl: "URL-ul Termenilor de utilizare"
thisYear: "An"
thisMonth: "Lună"
today: "Azi"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Pagini"
integration: "Integrare"
connectService: "Conectează"
disconnectService: "Deconectează"
enableLocalTimeline: "Activează cronologia locală"
enableGlobalTimeline: "Activeaza cronologia globală"
disablingTimelinesInfo: "Administratorii și Moderatorii vor avea mereu access la toate cronologiile, chiar dacă nu sunt activate."
registration: "Inregistrare"
enableRegistration: "Activează înregistrările pentru utilizatori noi"
invite: "Invită"
driveCapacityPerLocalAccount: "Capacitatea Drive-ului per utilizator local"
driveCapacityPerRemoteAccount: "Capacitatea Drive-ului per utilizator extern"
inMb: "În megabytes"
iconUrl: "URL-ul iconiței"
bannerUrl: "URL-ul imaginii de banner"
backgroundImageUrl: "URL-ul imaginii de fundal"
basicInfo: "Informații de bază"
pinnedUsers: "Utilizatori fixați"
pinnedUsersDescription: "Scrie utilizatorii, separați prin pauză de rând, care vor fi fixați pe pagina \"Explorează\"."
pinnedPages: "Pagini fixate"
pinnedPagesDescription: "Introdu linkurile Paginilor pe care le vrei fixate in vâruful paginii acestei instanțe, separate de pauze de rând."
pinnedClipId: "ID-ul clip-ului pe care să îl fixezi"
pinnedNotes: "Notă fixată"
hcaptcha: "hCaptcha"
enableHcaptcha: "Activează hCaptcha"
hcaptchaSiteKey: "Site key"
hcaptchaSecretKey: "Secret key"
recaptcha: "reCAPTCHA"
enableRecaptcha: "Activează reCAPTCHA"
recaptchaSiteKey: "Site key"
recaptchaSecretKey: "Secret key"
avoidMultiCaptchaConfirm: "Folosirea mai multor sisteme Captcha poate cauza interferență între acestea. Ai dori să dezactivezi alte sisteme Captcha acum active? Dacă preferi să rămână activate, apasă Anulare."
antennas: "Antene"
manageAntennas: "Gestionează Antenele"
name: "Nume"
antennaSource: "Sursa antenei"
antennaKeywords: "Cuvinte cheie ascultate"
antennaExcludeKeywords: "Cuvinte cheie excluse"
antennaKeywordsDescription: "Separă cu spații pentru o condiție ȘI sau cu o întrerupere de rând pentru o condiție SAU."
notifyAntenna: "Notifică-mă pentru note noi"
withFileAntenna: "Doar note cu fișiere"
enableServiceworker: "Activează ServiceWorker"
antennaUsersDescription: "Scrie un nume de utilizator per linie"
caseSensitive: "Sensibil la majuscule și minuscule"
withReplies: "Include răspunsuri"
connectedTo: "Următoarele conturi sunt conectate"
notesAndReplies: "Note și răspunsuri"
withFiles: "Incluzând fișiere"
silence: "Amuțește"
silenceConfirm: "Ești sigur că vrei să amuțești acest utilizator?"
unsilence: "Anulează amuțirea"
unsilenceConfirm: "Ești sigur că vrei să anulezi amuțirea acestui utilizator?"
popularUsers: "Utilizatori populari"
recentlyUpdatedUsers: "Utilizatori activi recent"
recentlyRegisteredUsers: "Utilizatori ce s-au alăturat recent"
recentlyDiscoveredUsers: "Utilizatori descoperiți recent"
exploreUsersCount: "Aici sunt {count} utilizatori"
exploreFediverse: "Explorează Fediverse-ul"
popularTags: "Taguri populare"
userList: "Liste"
about: "Despre"
aboutMisskey: "Despre Misskey"
administrator: "Administrator"
token: "Token"
twoStepAuthentication: "Autentificare în doi pași"
moderator: "Moderator"
nUsersMentioned: "Menționat de {n} utilizatori"
securityKey: "Cheie de securitate"
securityKeyName: "Numele cheii"
registerSecurityKey: "Înregistrează o cheie de securitate"
lastUsed: "Ultima utilizată"
unregister: "Dezînregistrează"
passwordLessLogin: "Autentificare fără parolă"
resetPassword: "Resetează parola"
newPasswordIs: "Noua parolă este \"{password}\""
reduceUiAnimation: "Redu animațiile interfeței"
share: "Distribuie"
notFound: "Nu a fost găsit"
notFoundDescription: "N-a fost găsită nicio pagină cu acest URL."
uploadFolder: "Folder implicit pentru încărcări"
cacheClear: "Golește cache-ul"
markAsReadAllNotifications: "Marchează toate notificările drept citit"
markAsReadAllUnreadNotes: "Marchează toate notele drept citit"
markAsReadAllTalkMessages: "Marchează toate mesajele drept citit"
help: "Ajutor"
inputMessageHere: "Introdu un mesaj aici"
close: "Închide"
group: "Grup"
groups: "Grupuri"
createGroup: "Crează un grup"
ownedGroups: "Grupuri deținute"
joinedGroups: "Grupuri alăturate"
invites: "Invită"
groupName: "Numele grupului"
members: "Membri"
transfer: "Transferă"
messagingWithUser: "Chat privat"
messagingWithGroup: "Chat de grup"
title: "Titlu"
text: "Text"
enable: "Activează"
next: "Următorul"
retype: "Introdu din nou"
noteOf: "Notă de {user}"
inviteToGroup: "Invită în grup"
quoteAttached: "Citat"
quoteQuestion: "Vrei să adaugi ca citat?"
noMessagesYet: "Niciun mesaj încă"
newMessageExists: "Ai mesaje noi"
onlyOneFileCanBeAttached: "Poți atașa un singur fișier la un mesaj"
signinRequired: "Te rog autentifică-te"
invitations: "Invită"
invitationCode: "Cod de invitație"
checking: "Se verifică..."
available: "Disponibil"
unavailable: "Indisponibil"
usernameInvalidFormat: "Poți folosi litere mari și mici, numere și underscore-uri."
tooShort: "Prea scurt"
tooLong: "Prea lung"
weakPassword: "Parolă slabă"
normalPassword: "Parolă medie"
strongPassword: "Parolă puternică"
passwordMatched: "Se potrivește!"
passwordNotMatched: "Nu se potrivește"
signinWith: "Autentifică-te cu {x}"
signinFailed: "Nu se poate autentifica. Numele de utilizator sau parola introduse sunt incorecte."
tapSecurityKey: "Apasă pe cheia ta de securitate."
or: "Sau"
language: "Limbă"
uiLanguage: "Limba interfeței"
groupInvited: "Ai fost invitat într-un grup"
aboutX: "Despre {x}"
useOsNativeEmojis: "Folosește emojiuri native OS-ului"
disableDrawer: "Nu folosi meniuri în stil sertar"
sounds: "Sunete"
listen: "Ascultă"
none: "Nimic"
showInPage: "Arată în pagină"
popout: "Scoate în afară"
volume: "Volum"
masterVolume: "Volumul principal"
details: "Detalii"
chooseEmoji: "Alege un emoji"
unableToProcess: "Această operație nu poate fi completată"
recentUsed: "Folosit recent"
install: "Instalează"
uninstall: "Dezinstalează"
installedApps: "Aplicații autorizate"
nothing: "Nu e nimic de văzut aici"
installedDate: "Autorizat la data de"
lastUsedDate: "Folosit ultima oara la"
state: "Stare"
sort: "Sortează"
ascendingOrder: "Crescător"
descendingOrder: "Descrescător"
scratchpad: "Scratchpad"
smtpHost: "Gazdă"
smtpUser: "Nume de utilizator"
smtpPass: "Parolă"
clearCache: "Golește cache-ul"
info: "Despre"
user: "Utilizatori"
searchByGoogle: "Caută"
_email:
_follow:
title: "te-a urmărit"
_mfm:
mention: "Mențiune"
quote: "Citează"
emoji: "Emoji personalizat"
search: "Caută"
_theme:
keys:
mention: "Mențiune"
renote: "Re-notează"
_sfx:
note: "Note"
notification: "Notificări"
chat: "Chat"
_widgets:
notifications: "Notificări"
timeline: "Cronologie"
activity: "Activitate"
federation: "Federație"
jobQueue: "coada de job-uri"
_cw:
show: "Incarcă mai mult"
_visibility:
home: "Acasă"
followers: "Urmăritori"
_profile:
name: "Nume"
username: "Nume de utilizator"
_exportOrImport:
followingList: "Urmărești"
muteList: "Amuțește"
blockingList: "Blochează"
userLists: "Liste"
_charts:
federation: "Federație"
_timelines:
home: "Acasă"
_pages:
blocks:
image: "Imagini"
script:
categories:
list: "Liste"
blocks:
_join:
arg1: "Liste"
_randomPick:
arg1: "Liste"
_dailyRandomPick:
arg1: "Liste"
_seedRandomPick:
arg2: "Liste"
_pick:
arg1: "Liste"
_listLen:
arg1: "Liste"
types:
array: "Liste"
_notification:
youWereFollowed: "te-a urmărit"
youWereInvitedToGroup: "Ai fost invitat într-un grup"
_types:
follow: "Urmărești"
mention: "Mențiune"
renote: "Re-notează"
quote: "Citează"
reaction: "Reacție"
_deck:
_columns:
notifications: "Notificări"
tl: "Cronologie"
antenna: "Antene"
list: "Liste"
mentions: "Mențiuni"

View file

@ -322,8 +322,6 @@ disablingTimelinesInfo: "У администраторов и модератор
registration: "Регистрация"
enableRegistration: "Разрешить регистрацию"
invite: "Пригласить"
proxyRemoteFiles: "Файлы с других сайтов пускать через прокси"
proxyRemoteFilesDescription: "Когда эта настройка включена, файлы с других серверов, которые не сохранены или удалены для освобождения места, будут проксироваться локально, а так же для них будут создаваться миниатюры. Эта настройка не затрагивает хранение на сервере."
driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя"
driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта"
inMb: "В мегабайтах"
@ -419,7 +417,6 @@ next: "Дальше"
retype: "Введите ещё раз"
noteOf: "Что пишет {user}"
inviteToGroup: "Пригласить в группу"
maxNoteTextLength: "Максимальная длина текста"
quoteAttached: "Цитата"
quoteQuestion: "Хотите добавить цитату?"
noMessagesYet: "Пока ни одного сообщения"
@ -818,6 +815,8 @@ leaveGroupConfirm: "Покинуть группу «{name}»?"
useDrawerReactionPickerForMobile: "Выдвижная палитра на мобильном устройстве"
welcomeBackWithName: "С возвращением, {name}!"
clickToFinishEmailVerification: "Пожалуйста, нажмите [{ok}], чтобы завершить подтверждение адреса электронной почты."
searchByGoogle: "Поиск"
indefinitely: "вечно"
_emailUnavailable:
used: "Уже используется"
format: "Неверный формат"

1
locales/si-LK.yml Normal file
View file

@ -0,0 +1 @@
---

View file

@ -325,8 +325,6 @@ disablingTimelinesInfo: "Administrátori a moderátori majú vždy prístup ku v
registration: "Registrácia"
enableRegistration: "Povoliť registráciu nových používateľov"
invite: "Pozvať"
proxyRemoteFiles: "Proxy vzdialených súborov"
proxyRemoteFilesDescription: "Ak je zapnuté, vzdialené súbory, ktoré nie sú uložené lokálne alebo boli odstránené kvôli obmedzeniam úložiska, budú vyžiadané cez proxy, vrátane generovani miniatúr. Neovplyvní to úložisko na serveri."
driveCapacityPerLocalAccount: "Kapacita disku pre používateľa"
driveCapacityPerRemoteAccount: "Kapacita disku pre vzdialeného používateľa"
inMb: "V megabajtoch"
@ -422,7 +420,6 @@ next: "Ďalší"
retype: "Zadajte znovu"
noteOf: "Poznámky používateľa {user}"
inviteToGroup: "Pozvať do skupiny"
maxNoteTextLength: "Maximálny počet znakov poznámky"
quoteAttached: "Citované"
quoteQuestion: "Pripojiť ako citát?"
noMessagesYet: "Zatiaľ žiadne správy"
@ -832,6 +829,16 @@ auto: "Automaticky"
themeColor: "Farba témy"
size: "Veľkosť"
numberOfColumn: "Počet stĺpcov"
searchByGoogle: "Hľadať cez Google"
instanceDefaultLightTheme: "Predvolená svetlá téma"
instanceDefaultDarkTheme: "Predvolená tmavá téma"
instanceDefaultThemeDescription: "Vložte kód témy v objektovom formáte"
mutePeriod: "Trvanie stíšenia"
indefinitely: "Navždy"
tenMinutes: "10 minút"
oneHour: "1 hodina"
oneDay: "1 deň"
oneWeek: "1 týždeň"
_emailUnavailable:
used: "Táto emailová adresa sa už používa"
format: "Formát emailovej adresy je nesprávny"
@ -1604,6 +1611,7 @@ _notification:
youReceivedFollowRequest: "Dostali ste žiadosť o sledovanie"
yourFollowRequestAccepted: "Vaša žiadosť o sledovanie bola prijatá"
youWereInvitedToGroup: "Pozvať do skupiny"
pollEnded: "Výsledky hlasovania sú k dispozícii."
_types:
all: "Všetky"
follow: "Sledujete"
@ -1613,6 +1621,7 @@ _notification:
quote: "Citovať"
reaction: "Reakcie"
pollVote: "Hlasy v hlasovaniach"
pollEnded: "Hlasovanie skončilo"
receiveFollowRequest: "Doručené žiadosti o sledovanie"
followRequestAccepted: "Schválené žiadosti o sledovanie"
groupInvited: "Pozvánky do skupín"

View file

@ -47,6 +47,7 @@ remove: "Sil"
smtpUser: "Kullanıcı Adı"
smtpPass: "Şifre"
user: "Kullanıcı"
searchByGoogle: "Arama"
_mfm:
search: "Arama"
_sfx:

View file

@ -1,5 +1,6 @@
---
_lang_: "ياپونچە"
search: "ئىزدەش"
searchByGoogle: "ئىزدەش"
_mfm:
search: "ئىزدەش"

View file

@ -312,8 +312,6 @@ disablingTimelinesInfo: "Адміністратори та модератори
registration: "Реєстрація"
enableRegistration: "Дозволити реєстрацію"
invite: "Запросити"
proxyRemoteFiles: "Проксувати файли з інших інстансів"
proxyRemoteFilesDescription: "При увімкненні віддалені файли, які не зберігаються локально або були видалені через недостатнії обсяг пам'яті, будуть локально проксовані включаючи обкладинки. Це налаштування не впливає на пам'ять серверу. "
driveCapacityPerLocalAccount: "Об'єм диска на одного локального користувача"
driveCapacityPerRemoteAccount: "Об'єм диска на одного віддаленого користувача"
inMb: "В мегабайтах"
@ -407,7 +405,6 @@ next: "Далі"
retype: "Введіть ще раз"
noteOf: "Нотатка {user}"
inviteToGroup: "Запрошення до групи"
maxNoteTextLength: "Максимальна довжина нотатки"
quoteAttached: "Цитата"
quoteQuestion: "Ви хочете додати цитату?"
noMessagesYet: "Ще немає повідомлень"
@ -688,6 +685,8 @@ global: "Глобальна"
sent: "Відправити"
hashtags: "Хештеґ"
hide: "Сховати"
searchByGoogle: "Пошук"
indefinitely: "Ніколи"
_ad:
back: "Назад"
_gallery:

48
locales/vi-VN.yml Normal file
View file

@ -0,0 +1,48 @@
---
_lang_: "Tiếng Việt"
headlineMisskey: "Mạng xã hội liên hợp"
monthAndDay: "{day} tháng {month}"
search: "Tìm kiếm"
notifications: "Thông báo"
username: "Tên người dùng"
password: "Mật khẩu"
forgotPassword: "Quên mật khẩu"
ok: "Đồng ý"
renotedBy: "Chia sẻ bởi {user}"
flagAsBot: "Đánh dấu đây là tài khoản bot"
searchWith: "Tìm kiếm: {q}"
followConfirm: "Bạn có chắc muốn theo dõi {name}"
cpuAndMemory: "CPU và Dung lượng"
dayX: "{day}"
yearX: "{year}"
aboutMisskey: "Về Misskey"
smtpUser: "Tên người dùng"
smtpPass: "Mật khẩu"
reportAbuseOf: "Báo cáo {name}"
renotedCount: "Lượt chia sẻ"
translatedFrom: "Dịch từ {x}"
searchByGoogle: "Google"
_mfm:
search: "Tìm kiếm"
_theme:
installed: "{name} đã được cài đặt"
_sfx:
notification: "Thông báo"
_ago:
unknown: "Không rõ"
future: "Tương lai"
justNow: "Vừa xong"
secondsAgo: "{n}s trước"
minutesAgo: "{n} phút trước"
hoursAgo: "{n} giờ trước"
daysAgo: "{n} ngày trước"
weeksAgo: "{n} tuần trước"
monthsAgo: "{n} tháng trước"
yearsAgo: "{n} năm trước"
_widgets:
notifications: "Thông báo"
_profile:
username: "Tên người dùng"
_deck:
_columns:
notifications: "Thông báo"

View file

@ -325,8 +325,6 @@ disablingTimelinesInfo: "即使时间线功能被禁用,出于便利性的原
registration: "注册"
enableRegistration: "允许新用户注册"
invite: "邀请"
proxyRemoteFiles: "代理远程文件"
proxyRemoteFilesDescription: "启用此设置后,由于超出存储容量而导致未保存被删除的远程文件将被本地代理,并且会生成缩略图。不会影响服务器的存储。"
driveCapacityPerLocalAccount: "每个用户的网盘空间"
driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
inMb: "以兆字节(MegaByte)为单位"
@ -422,7 +420,6 @@ next: "下一个"
retype: "重新输入"
noteOf: "{user}的帖子"
inviteToGroup: "群组邀请"
maxNoteTextLength: "帖子的字数限制"
quoteAttached: "已引用"
quoteQuestion: "是否引用此链接内容?"
noMessagesYet: "现在没有新的聊天"
@ -833,6 +830,16 @@ auto: "自动"
themeColor: "主题颜色"
size: "大小"
numberOfColumn: "列数"
searchByGoogle: "Google"
instanceDefaultLightTheme: "实例默认浅色主题"
instanceDefaultDarkTheme: "实例默认深色主题"
instanceDefaultThemeDescription: "以对象格式键入主题代码"
mutePeriod: "屏蔽期限"
indefinitely: "永久"
tenMinutes: "10分钟"
oneHour: "1小时"
oneDay: "1天"
oneWeek: "1周"
_emailUnavailable:
used: "已经被使用过"
format: "无效的格式"
@ -1210,7 +1217,7 @@ _poll:
noMore: "无法再添加更多了"
canMultipleVote: "允许多个投票"
expiration: "截止时间"
infinite: "不限时间"
infinite: "永久"
at: "指定日期"
after: "指定时间"
deadlineDate: "截止日期"
@ -1605,6 +1612,7 @@ _notification:
youReceivedFollowRequest: "您有新的关注请求"
yourFollowRequestAccepted: "您的关注请求已通过"
youWereInvitedToGroup: "您有新的群组邀请"
pollEnded: "问卷调查结果已生成。"
_types:
all: "全部"
follow: "关注中"
@ -1614,6 +1622,7 @@ _notification:
quote: "引用"
reaction: "回应"
pollVote: "问卷调查被投票"
pollEnded: "问卷调查结束"
receiveFollowRequest: "收到关注请求"
followRequestAccepted: "关注请求已通过"
groupInvited: "加入群组邀请"

View file

@ -318,8 +318,6 @@ disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調
registration: "註冊"
enableRegistration: "開啟新使用者註冊"
invite: "邀請"
proxyRemoteFiles: "遠端代理檔案"
proxyRemoteFilesDescription: "啟用此設置後,由於超出存儲容量而未保存或刪除的遠程文件將被本地代理,並且將生成預覽圖。這不影響伺服器的存儲。"
driveCapacityPerLocalAccount: "每個本地用戶的雲端空間大小"
driveCapacityPerRemoteAccount: "每個非本地用戶的雲端容量"
inMb: "以Mbps為單位"
@ -415,7 +413,6 @@ next: "下一步"
retype: "重新輸入"
noteOf: "{user}的貼文"
inviteToGroup: "邀請至群組"
maxNoteTextLength: "貼文的字數限制"
quoteAttached: "引用"
quoteQuestion: "是否要引用?"
noMessagesYet: "沒有訊息"
@ -750,6 +747,8 @@ global: "公開"
sent: "發送"
hashtags: "#tag"
hide: "隱藏"
searchByGoogle: "搜尋"
indefinitely: "無期限"
_ffVisibility:
public: "發佈"
_ad:

View file

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "12.107.0",
"version": "12.108.1",
"codename": "indigo",
"repository": {
"type": "git",
@ -13,8 +13,7 @@
"start": "cd packages/backend && node --experimental-json-modules ./built/index.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node --experimental-json-modules ./built/index.js",
"init": "npm run migrate",
"ormconfig": "node ./packages/backend/ormconfig.js",
"migrate": "cd packages/backend && npx typeorm migration:run",
"migrate": "cd packages/backend && npx typeorm migration:run -d ormconfig.js",
"migrateandstart": "npm run migrate && npm run start",
"gulp": "gulp build",
"watch": "npm run dev",
@ -42,10 +41,10 @@
"js-yaml": "4.1.0"
},
"devDependencies": {
"@typescript-eslint/parser": "5.12.0",
"@typescript-eslint/parser": "5.16.0",
"cross-env": "7.0.3",
"cypress": "9.5.0",
"cypress": "9.5.2",
"start-server-and-test": "1.14.0",
"typescript": "4.5.5"
"typescript": "4.6.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -0,0 +1,13 @@
export class muteExpiresAt1646387162108 {
name = 'muteExpiresAt1646387162108'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "muting" ADD "expiresAt" TIMESTAMP WITH TIME ZONE`);
await queryRunner.query(`CREATE INDEX "IDX_c1fd1c3dfb0627aa36c253fd14" ON "muting" ("expiresAt") `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_c1fd1c3dfb0627aa36c253fd14"`);
await queryRunner.query(`ALTER TABLE "muting" DROP COLUMN "expiresAt"`);
}
}

View file

@ -0,0 +1,18 @@
export class pollEndedNotification1646549089451 {
name = 'pollEndedNotification1646549089451'
async up(queryRunner) {
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
}
async down(queryRunner) {
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
}
}

View file

@ -0,0 +1,13 @@
export class chartFederationActive1646633030285 {
name = 'chartFederationActive1646633030285'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___active"`);
await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___active"`);
}
}

View file

@ -0,0 +1,13 @@
export class removeInstanceDriveColumns1646655454495 {
name = 'removeInstanceDriveColumns1646655454495'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "driveUsage"`);
await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "driveFiles"`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "instance" ADD "driveFiles" integer NOT NULL DEFAULT '0'`);
await queryRunner.query(`ALTER TABLE "instance" ADD "driveUsage" bigint NOT NULL DEFAULT '0'`);
}
}

View file

@ -0,0 +1,21 @@
export class chartFederationActiveSubPub1646732390560 {
name = 'chartFederationActiveSubPub1646732390560'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___active"`);
await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___active"`);
await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___subActive" smallint NOT NULL DEFAULT '0'`);
await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___pubActive" smallint NOT NULL DEFAULT '0'`);
await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___subActive" smallint NOT NULL DEFAULT '0'`);
await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___pubActive" smallint NOT NULL DEFAULT '0'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___pubActive"`);
await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___subActive"`);
await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___pubActive"`);
await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___subActive"`);
await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___active" smallint NOT NULL DEFAULT '0'`);
}
}

View file

@ -1,7 +1,8 @@
import { DataSource } from 'typeorm';
import config from './built/config/index.js';
import { entities } from './built/db/postgre.js';
export default {
export default new DataSource({
type: 'postgres',
host: config.db.host,
port: config.db.port,
@ -11,7 +12,4 @@ export default {
extra: config.db.extra,
entities: entities,
migrations: ['migration/*.js'],
cli: {
migrationsDir: 'migration'
}
};
});

View file

@ -3,7 +3,6 @@
"private": true,
"type": "module",
"scripts": {
"init": "npm run migrate",
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
"lint": "eslint --quiet src/**/*.ts",
@ -15,12 +14,12 @@
"lodash": "^4.17.21"
},
"dependencies": {
"@discordapp/twemoji": "13.1.0",
"@discordapp/twemoji": "13.1.1",
"@elastic/elasticsearch": "7.11.0",
"@koa/cors": "3.1.0",
"@koa/multer": "3.0.0",
"@koa/router": "10.1.1",
"@sinonjs/fake-timers": "9.1.0",
"@koa/router": "9.0.1",
"@sinonjs/fake-timers": "9.1.1",
"@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.8",
@ -31,7 +30,7 @@
"@types/jsdom": "16.2.14",
"@types/jsonld": "1.5.6",
"@types/koa": "2.13.4",
"@types/koa-bodyparser": "4.3.5",
"@types/koa-bodyparser": "4.3.7",
"@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21",
"@types/koa-logger": "3.1.2",
@ -42,7 +41,7 @@
"@types/koa__multer": "2.0.4",
"@types/koa__router": "8.0.11",
"@types/mocha": "9.1.0",
"@types/node": "17.0.19",
"@types/node": "17.0.23",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.4",
"@types/oauth": "0.9.1",
@ -56,9 +55,8 @@
"@types/redis": "4.0.11",
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.6.2",
"@types/seedrandom": "3.0.1",
"@types/sharp": "0.29.5",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sharp": "0.30.0",
"@types/sinonjs__fake-timers": "8.1.2",
"@types/speakeasy": "2.0.7",
"@types/throttle-debounce": "2.1.0",
"@types/tinycolor2": "1.4.3",
@ -66,44 +64,44 @@
"@types/uuid": "8.3.4",
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.2.3",
"@typescript-eslint/eslint-plugin": "5.12.1",
"@typescript-eslint/parser": "5.12.1",
"@types/ws": "8.5.3",
"@typescript-eslint/eslint-plugin": "5.16.0",
"@typescript-eslint/parser": "5.16.0",
"@bull-board/koa": "3.10.1",
"abort-controller": "3.0.0",
"ajv": "8.10.0",
"ajv": "8.11.0",
"archiver": "5.3.0",
"autobind-decorator": "2.4.0",
"autwh": "0.1.0",
"aws-sdk": "2.1079.0",
"aws-sdk": "2.1100.0",
"bcryptjs": "2.4.3",
"blurhash": "1.1.5",
"broadcast-channel": "4.10.0",
"bull": "4.6.2",
"bull": "4.8.1",
"cacheable-lookup": "6.0.4",
"cafy": "15.2.1",
"cbor": "8.1.0",
"chalk": "5.0.0",
"chalk-template": "0.3.1",
"chalk": "5.0.1",
"chalk-template": "0.4.0",
"cli-highlight": "2.1.11",
"color-convert": "2.0.1",
"content-disposition": "0.5.4",
"date-fns": "2.28.0",
"deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1",
"eslint": "8.9.0",
"eslint": "8.12.0",
"eslint-plugin-import": "2.25.4",
"eventemitter3": "4.0.7",
"feed": "4.2.2",
"file-type": "16.5.3",
"file-type": "17.1.1",
"fluent-ffmpeg": "2.1.2",
"got": "11.8.2",
"got": "12.0.3",
"hpagent": "0.1.2",
"http-signature": "1.3.6",
"ip-cidr": "3.0.4",
"is-svg": "4.3.2",
"js-yaml": "4.1.0",
"jsdom": "19.0.0",
"json5": "2.2.0",
"json5": "2.2.1",
"json5-loader": "4.0.1",
"jsonld": "5.2.0",
"jsrsasign": "8.0.20",
@ -117,14 +115,14 @@
"koa-slow": "2.1.0",
"koa-views": "7.0.2",
"mfm-js": "0.21.0",
"mime-types": "2.1.34",
"mime-types": "2.1.35",
"misskey-js": "0.0.14",
"mocha": "9.2.1",
"mocha": "9.2.2",
"ms": "3.0.0-canary.1",
"multer": "1.4.4",
"nested-property": "4.0.0",
"node-fetch": "2.6.7",
"nodemailer": "6.7.2",
"node-fetch": "3.2.3",
"nodemailer": "6.7.3",
"os-utils": "0.0.14",
"parse5": "6.0.1",
"pg": "8.7.3",
@ -147,25 +145,25 @@
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sanitize-html": "2.7.0",
"seedrandom": "3.0.5",
"sharp": "0.30.1",
"semver": "7.3.5",
"sharp": "0.30.3",
"speakeasy": "2.0.0",
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"style-loader": "3.3.1",
"summaly": "2.5.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.11.4",
"systeminformation": "5.11.9",
"throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2",
"tmp": "0.2.1",
"ts-loader": "9.2.6",
"ts-node": "10.5.0",
"ts-loader": "9.2.8",
"ts-node": "10.7.0",
"tsc-alias": "1.4.1",
"tsconfig-paths": "3.12.0",
"twemoji-parser": "13.1.0",
"typeorm": "0.2.44",
"typescript": "4.5.5",
"tsconfig-paths": "3.14.1",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.4",
"typescript": "4.6.3",
"ulid": "2.3.0",
"unzipper": "0.10.11",
"uuid": "8.3.2",
@ -175,7 +173,7 @@
"xev": "2.0.1"
},
"devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.83",
"@redocly/openapi-core": "1.0.0-beta.90",
"@types/fluent-ffmpeg": "2.1.20",
"cross-env": "7.0.3",
"execa": "6.1.0"

View file

@ -6,7 +6,7 @@ import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
import * as portscanner from 'portscanner';
import { getConnection } from 'typeorm';
import semver from 'semver';
import Logger from '@/services/logger.js';
import loadConfig from '@/config/load.js';
@ -14,7 +14,7 @@ import { Config } from '@/config/types.js';
import { lessThan } from '@/prelude/array.js';
import { envOption } from '../env.js';
import { showMachineInfo } from '@/misc/show-machine-info.js';
import { initDb } from '../db/postgre.js';
import { db, initDb } from '../db/postgre.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -88,10 +88,6 @@ export async function masterMain() {
}
}
const runningNodejsVersion = process.version.slice(1).split('.').map(x => parseInt(x, 10));
const requiredNodejsVersion = [11, 7, 0];
const satisfyNodejsVersion = !lessThan(runningNodejsVersion, requiredNodejsVersion);
function showEnvironment(): void {
const env = process.env.NODE_ENV;
const logger = bootLogger.createSubLogger('env');
@ -108,10 +104,11 @@ function showEnvironment(): void {
function showNodejsVersion(): void {
const nodejsLogger = bootLogger.createSubLogger('nodejs');
nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`);
nodejsLogger.info(`Version ${process.version} detected.`);
if (!satisfyNodejsVersion) {
nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true);
const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, 'utf-8').trim();
if (semver.lt(process.version, minVersion)) {
nodejsLogger.error(`At least Node.js ${minVersion} required!`);
process.exit(1);
}
}
@ -146,7 +143,7 @@ async function connectDb(): Promise<void> {
try {
dbLogger.info('Connecting...');
await initDb();
const v = await getConnection().query('SHOW server_version').then(x => x[0].server_version);
const v = await db.query('SHOW server_version').then(x => x[0].server_version);
dbLogger.succ(`Connected: v${v}`);
} catch (e) {
dbLogger.error('Cannot connect', null, true);

View file

@ -6,7 +6,6 @@ export type Source = {
feedback_url?: string;
url: string;
port: number;
https?: { [x: string]: string };
disableHsts?: boolean;
db: {
host: string;

View file

@ -2,9 +2,10 @@
import pg from 'pg';
pg.types.setTypeParser(20, Number);
import { createConnection, Logger, getConnection } from 'typeorm';
import { Logger, DataSource } from 'typeorm';
import * as highlight from 'cli-highlight';
import config from '@/config/index.js';
import { envOption } from '../env.js';
import { dbLogger } from './logger.js';
@ -61,7 +62,6 @@ import { Antenna } from '@/models/entities/antenna.js';
import { AntennaNote } from '@/models/entities/antenna-note.js';
import { PromoNote } from '@/models/entities/promo-note.js';
import { PromoRead } from '@/models/entities/promo-read.js';
import { envOption } from '../env.js';
import { Relay } from '@/models/entities/relay.js';
import { MutedNote } from '@/models/entities/muted-note.js';
import { Channel } from '@/models/entities/channel.js';
@ -74,7 +74,7 @@ import { UserPending } from '@/models/entities/user-pending.js';
import { entities as charts } from '@/services/chart/entities.js';
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
class MyCustomLogger implements Logger {
private highlight(sql: string) {
@ -84,9 +84,7 @@ class MyCustomLogger implements Logger {
}
public logQuery(query: string, parameters?: any[]) {
if (envOption.verbose) {
sqlLogger.info(this.highlight(query));
}
sqlLogger.info(this.highlight(query).substring(0, 100));
}
public logQueryError(error: string, query: string, parameters?: any[]) {
@ -176,52 +174,51 @@ export const entities = [
...charts,
];
export function initDb(justBorrow = false, sync = false, forceRecreate = false) {
if (!forceRecreate) {
try {
const conn = getConnection();
return Promise.resolve(conn);
} catch (e) {}
}
const log = process.env.NODE_ENV !== 'production';
const log = process.env.NODE_ENV != 'production';
export const db = new DataSource({
type: 'postgres',
host: config.db.host,
port: config.db.port,
username: config.db.user,
password: config.db.pass,
database: config.db.db,
extra: {
statement_timeout: 1000 * 10,
...config.db.extra,
},
synchronize: process.env.NODE_ENV === 'test',
dropSchema: process.env.NODE_ENV === 'test',
cache: !config.db.disableCache ? {
type: 'redis',
options: {
host: config.redis.host,
port: config.redis.port,
password: config.redis.pass,
prefix: `${config.redis.prefix}:query:`,
db: config.redis.db || 0,
},
} : false,
logging: log,
logger: log ? new MyCustomLogger() : undefined,
maxQueryExecutionTime: 300,
entities: entities,
migrations: ['../../migration/*.js'],
});
return createConnection({
type: 'postgres',
host: config.db.host,
port: config.db.port,
username: config.db.user,
password: config.db.pass,
database: config.db.db,
extra: config.db.extra,
synchronize: process.env.NODE_ENV === 'test' || sync,
dropSchema: process.env.NODE_ENV === 'test' && !justBorrow,
cache: !config.db.disableCache ? {
type: 'redis',
options: {
host: config.redis.host,
port: config.redis.port,
password: config.redis.pass,
prefix: `${config.redis.prefix}:query:`,
db: config.redis.db || 0,
},
} : false,
logging: log,
logger: log ? new MyCustomLogger() : undefined,
entities: entities,
});
export async function initDb() {
await db.connect();
}
export async function resetDb() {
const reset = async () => {
const conn = await getConnection();
const tables = await conn.query(`SELECT relname AS "table"
const tables = await db.query(`SELECT relname AS "table"
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind = 'r'
AND nspname !~ '^pg_toast';`);
for (const table of tables) {
await conn.query(`DELETE FROM "${table.table}" CASCADE`);
await db.query(`DELETE FROM "${table.table}" CASCADE`);
}
};

View file

@ -1,5 +1,5 @@
export class Cache<T> {
private cache: Map<string | null, { date: number; value: T; }>;
public cache: Map<string | null, { date: number; value: T; }>;
private lifetime: number;
constructor(lifetime: Cache<never>['lifetime']) {
@ -28,16 +28,52 @@ export class Cache<T> {
this.cache.delete(key);
}
public async fetch(key: string | null, fetcher: () => Promise<T>): Promise<T> {
/**
* fetcherを呼び出して結果をキャッシュ&
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
// Cache HIT
return cachedValue;
if (validator) {
if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
}
// Cache MISS
const value = await fetcher();
this.set(key, value);
return value;
}
/**
* fetcherを呼び出して結果をキャッシュ&
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
*/
public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
const cachedValue = this.get(key);
if (cachedValue !== undefined) {
if (validator) {
if (validator(cachedValue)) {
// Cache HIT
return cachedValue;
}
} else {
// Cache HIT
return cachedValue;
}
}
// Cache MISS
const value = await fetcher();
if (value !== undefined) {
this.set(key, value);
}
return value;
}
}

View file

@ -1,17 +1,26 @@
import { Antenna } from '@/models/entities/antenna.js';
import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js';
import { UserListJoinings, UserGroupJoinings } from '@/models/index.js';
import { UserListJoinings, UserGroupJoinings, Blockings } from '@/models/index.js';
import { getFullApAccount } from './convert-host.js';
import * as Acct from '@/misc/acct.js';
import { Packed } from './schema.js';
import { Cache } from './cache.js';
const blockingCache = new Cache<User['id'][]>(1000 * 60 * 5);
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
/**
* noteUserFollowers / antennaUserFollowing
*/
export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
if (note.visibility === 'specified') return false;
// アンテナ作成者がノート作成者にブロックされていたらスキップ
const blockings = await blockingCache.fetch(noteUser.id, () => Blockings.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
if (blockings.some(blocking => blocking === antenna.userId)) return false;
if (note.visibility === 'followers') {
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
@ -23,15 +32,15 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No
if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
} else if (antenna.src === 'list') {
const listUsers = (await UserListJoinings.find({
const listUsers = (await UserListJoinings.findBy({
userListId: antenna.userListId!,
})).map(x => x.userId);
if (!listUsers.includes(note.userId)) return false;
} else if (antenna.src === 'group') {
const joining = await UserGroupJoinings.findOneOrFail(antenna.userGroupJoiningId!);
const joining = await UserGroupJoinings.findOneByOrFail({ id: antenna.userGroupJoiningId! });
const groupUsers = (await UserGroupJoinings.find({
const groupUsers = (await UserGroupJoinings.findBy({
userGroupId: joining.userGroupId,
})).map(x => x.userId);

View file

@ -6,7 +6,7 @@ import { httpAgent, httpsAgent, StatusError } from './fetch.js';
import config from '@/config/index.js';
import chalk from 'chalk';
import Logger from '@/services/logger.js';
import * as IPCIDR from 'ip-cidr';
import IPCIDR from 'ip-cidr';
import PrivateIp from 'private-ip';
const pipeline = util.promisify(stream.pipeline);

View file

@ -1,19 +1,21 @@
import { db } from '@/db/postgre.js';
import { Meta } from '@/models/entities/meta.js';
import { getConnection } from 'typeorm';
let cache: Meta;
export async function fetchMeta(noCache = false): Promise<Meta> {
if (!noCache && cache) return cache;
return await getConnection().transaction(async transactionalEntityManager => {
return await db.transaction(async transactionalEntityManager => {
// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
const meta = await transactionalEntityManager.findOne(Meta, {
const metas = await transactionalEntityManager.find(Meta, {
order: {
id: 'DESC',
},
});
const meta = metas[0];
if (meta) {
cache = meta;
return meta;

View file

@ -5,5 +5,5 @@ import { Users } from '@/models/index.js';
export async function fetchProxyAccount(): Promise<ILocalUser | null> {
const meta = await fetchMeta();
if (meta.proxyAccountId == null) return null;
return await Users.findOneOrFail(meta.proxyAccountId) as ILocalUser;
return await Users.findOneByOrFail({ id: meta.proxyAccountId }) as ILocalUser;
}

View file

@ -2,7 +2,7 @@ import * as fs from 'node:fs';
import * as crypto from 'node:crypto';
import * as stream from 'node:stream';
import * as util from 'node:util';
import fileType from 'file-type';
import { fileTypeFromFile } from 'file-type';
import isSvg from 'is-svg';
import probeImageSize from 'probe-image-size';
import sharp from 'sharp';
@ -109,7 +109,7 @@ export async function detectType(path: string): Promise<{
return TYPE_OCTET_STREAM;
}
const type = await fileType.fromFile(path);
const type = await fileTypeFromFile(path);
if (type) {
// XMLはSVGかもしれない

View file

@ -6,5 +6,5 @@ import { Cache } from './cache.js';
const cache = new Cache<UserKeypair>(Infinity);
export async function getUserKeypair(userId: User['id']): Promise<UserKeypair> {
return await cache.fetch(userId, () => UserKeypairs.findOneOrFail(userId));
return await cache.fetch(userId, () => UserKeypairs.findOneByOrFail({ userId: userId }));
}

View file

@ -1,4 +1,4 @@
import { In } from 'typeorm';
import { In, IsNull } from 'typeorm';
import { Emojis } from '@/models/index.js';
import { Emoji } from '@/models/entities/emoji.js';
import { Note } from '@/models/entities/note.js';
@ -52,9 +52,9 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
const { name, host } = parseEmojiStr(emojiName, noteUserHost);
if (name == null) return null;
const queryOrNull = async () => (await Emojis.findOne({
const queryOrNull = async () => (await Emojis.findOneBy({
name,
host,
host: host ?? IsNull(),
})) || null;
const emoji = await cache.fetch(`${name} ${host}`, queryOrNull);
@ -112,7 +112,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null
for (const host of hosts) {
emojisQuery.push({
name: In(notCachedEmojis.filter(e => e.host === host).map(e => e.name)),
host: host,
host: host ?? IsNull(),
});
}
const _emojis = emojisQuery.length > 0 ? await Emojis.find({

View file

@ -3,6 +3,7 @@ import { emojiRegex } from './emoji-regex.js';
import { fetchMeta } from './fetch-meta.js';
import { Emojis } from '@/models/index.js';
import { toPunyNullable } from './convert-host.js';
import { IsNull } from 'typeorm';
const legacies: Record<string, string> = {
'like': '👍',
@ -74,8 +75,8 @@ export async function toDbReaction(reaction?: string | null, reacterHost?: strin
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
if (custom) {
const name = custom[1];
const emoji = await Emojis.findOne({
host: reacterHost || null,
const emoji = await Emojis.findOneBy({
host: reacterHost ?? IsNull(),
name,
});

View file

@ -59,22 +59,6 @@ export class Instance {
})
public followersCount: number;
/**
* 使
*/
@Column('bigint', {
default: 0,
})
public driveUsage: number;
/**
*
*/
@Column('integer', {
default: 0,
})
public driveFiles: number;
/**
*
*/

View file

@ -14,6 +14,13 @@ export class Muting {
})
public createdAt: Date;
@Index()
@Column('timestamp with time zone', {
nullable: true,
default: null,
})
public expiresAt: Date | null;
@Index()
@Column({
...id(),

View file

@ -59,7 +59,8 @@ export class Notification {
* renote - (Watchしている)稿Renoteされた
* quote - (Watchしている)稿Renoteされた
* reaction - (Watchしている)稿
* pollVote - (Watchしている)稿
* pollVote - (Watchしている)稿
* pollEnded -
* receiveFollowRequest -
* followRequestAccepted -
* groupInvited -

View file

@ -234,3 +234,9 @@ export interface ILocalUser extends User {
export interface IRemoteUser extends User {
host: string;
}
export type CacheableLocalUser = ILocalUser;
export type CacheableRemoteUser = IRemoteUser;
export type CacheableUser = CacheableLocalUser | CacheableRemoteUser;

View file

@ -1,4 +1,6 @@
import { getRepository, getCustomRepository } from 'typeorm';
import { } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Announcement } from './entities/announcement.js';
import { AnnouncementRead } from './entities/announcement-read.js';
import { Instance } from './entities/instance.js';
@ -63,65 +65,65 @@ import { PasswordResetRequest } from './entities/password-reset-request.js';
import { UserPending } from './entities/user-pending.js';
import { InstanceRepository } from './repositories/instance.js';
export const Announcements = getRepository(Announcement);
export const AnnouncementReads = getRepository(AnnouncementRead);
export const Apps = getCustomRepository(AppRepository);
export const Notes = getCustomRepository(NoteRepository);
export const NoteFavorites = getCustomRepository(NoteFavoriteRepository);
export const NoteWatchings = getRepository(NoteWatching);
export const NoteThreadMutings = getRepository(NoteThreadMuting);
export const NoteReactions = getCustomRepository(NoteReactionRepository);
export const NoteUnreads = getRepository(NoteUnread);
export const Polls = getRepository(Poll);
export const PollVotes = getRepository(PollVote);
export const Users = getCustomRepository(UserRepository);
export const UserProfiles = getRepository(UserProfile);
export const UserKeypairs = getRepository(UserKeypair);
export const UserPendings = getRepository(UserPending);
export const AttestationChallenges = getRepository(AttestationChallenge);
export const UserSecurityKeys = getRepository(UserSecurityKey);
export const UserPublickeys = getRepository(UserPublickey);
export const UserLists = getCustomRepository(UserListRepository);
export const UserListJoinings = getRepository(UserListJoining);
export const UserGroups = getCustomRepository(UserGroupRepository);
export const UserGroupJoinings = getRepository(UserGroupJoining);
export const UserGroupInvitations = getCustomRepository(UserGroupInvitationRepository);
export const UserNotePinings = getRepository(UserNotePining);
export const UsedUsernames = getRepository(UsedUsername);
export const Followings = getCustomRepository(FollowingRepository);
export const FollowRequests = getCustomRepository(FollowRequestRepository);
export const Instances = getCustomRepository(InstanceRepository);
export const Emojis = getCustomRepository(EmojiRepository);
export const DriveFiles = getCustomRepository(DriveFileRepository);
export const DriveFolders = getCustomRepository(DriveFolderRepository);
export const Notifications = getCustomRepository(NotificationRepository);
export const Metas = getRepository(Meta);
export const Mutings = getCustomRepository(MutingRepository);
export const Blockings = getCustomRepository(BlockingRepository);
export const SwSubscriptions = getRepository(SwSubscription);
export const Hashtags = getCustomRepository(HashtagRepository);
export const AbuseUserReports = getCustomRepository(AbuseUserReportRepository);
export const RegistrationTickets = getRepository(RegistrationTicket);
export const AuthSessions = getCustomRepository(AuthSessionRepository);
export const AccessTokens = getRepository(AccessToken);
export const Signins = getCustomRepository(SigninRepository);
export const MessagingMessages = getCustomRepository(MessagingMessageRepository);
export const Pages = getCustomRepository(PageRepository);
export const PageLikes = getCustomRepository(PageLikeRepository);
export const GalleryPosts = getCustomRepository(GalleryPostRepository);
export const GalleryLikes = getCustomRepository(GalleryLikeRepository);
export const ModerationLogs = getCustomRepository(ModerationLogRepository);
export const Clips = getCustomRepository(ClipRepository);
export const ClipNotes = getRepository(ClipNote);
export const Antennas = getCustomRepository(AntennaRepository);
export const AntennaNotes = getRepository(AntennaNote);
export const PromoNotes = getRepository(PromoNote);
export const PromoReads = getRepository(PromoRead);
export const Relays = getCustomRepository(RelayRepository);
export const MutedNotes = getRepository(MutedNote);
export const Channels = getCustomRepository(ChannelRepository);
export const ChannelFollowings = getRepository(ChannelFollowing);
export const ChannelNotePinings = getRepository(ChannelNotePining);
export const RegistryItems = getRepository(RegistryItem);
export const Ads = getRepository(Ad);
export const PasswordResetRequests = getRepository(PasswordResetRequest);
export const Announcements = db.getRepository(Announcement);
export const AnnouncementReads = db.getRepository(AnnouncementRead);
export const Apps = (AppRepository);
export const Notes = (NoteRepository);
export const NoteFavorites = (NoteFavoriteRepository);
export const NoteWatchings = db.getRepository(NoteWatching);
export const NoteThreadMutings = db.getRepository(NoteThreadMuting);
export const NoteReactions = (NoteReactionRepository);
export const NoteUnreads = db.getRepository(NoteUnread);
export const Polls = db.getRepository(Poll);
export const PollVotes = db.getRepository(PollVote);
export const Users = (UserRepository);
export const UserProfiles = db.getRepository(UserProfile);
export const UserKeypairs = db.getRepository(UserKeypair);
export const UserPendings = db.getRepository(UserPending);
export const AttestationChallenges = db.getRepository(AttestationChallenge);
export const UserSecurityKeys = db.getRepository(UserSecurityKey);
export const UserPublickeys = db.getRepository(UserPublickey);
export const UserLists = (UserListRepository);
export const UserListJoinings = db.getRepository(UserListJoining);
export const UserGroups = (UserGroupRepository);
export const UserGroupJoinings = db.getRepository(UserGroupJoining);
export const UserGroupInvitations = (UserGroupInvitationRepository);
export const UserNotePinings = db.getRepository(UserNotePining);
export const UsedUsernames = db.getRepository(UsedUsername);
export const Followings = (FollowingRepository);
export const FollowRequests = (FollowRequestRepository);
export const Instances = (InstanceRepository);
export const Emojis = (EmojiRepository);
export const DriveFiles = (DriveFileRepository);
export const DriveFolders = (DriveFolderRepository);
export const Notifications = (NotificationRepository);
export const Metas = db.getRepository(Meta);
export const Mutings = (MutingRepository);
export const Blockings = (BlockingRepository);
export const SwSubscriptions = db.getRepository(SwSubscription);
export const Hashtags = (HashtagRepository);
export const AbuseUserReports = (AbuseUserReportRepository);
export const RegistrationTickets = db.getRepository(RegistrationTicket);
export const AuthSessions = (AuthSessionRepository);
export const AccessTokens = db.getRepository(AccessToken);
export const Signins = (SigninRepository);
export const MessagingMessages = (MessagingMessageRepository);
export const Pages = (PageRepository);
export const PageLikes = (PageLikeRepository);
export const GalleryPosts = (GalleryPostRepository);
export const GalleryLikes = (GalleryLikeRepository);
export const ModerationLogs = (ModerationLogRepository);
export const Clips = (ClipRepository);
export const ClipNotes = db.getRepository(ClipNote);
export const Antennas = (AntennaRepository);
export const AntennaNotes = db.getRepository(AntennaNote);
export const PromoNotes = db.getRepository(PromoNote);
export const PromoReads = db.getRepository(PromoRead);
export const Relays = (RelayRepository);
export const MutedNotes = db.getRepository(MutedNote);
export const Channels = (ChannelRepository);
export const ChannelFollowings = db.getRepository(ChannelFollowing);
export const ChannelNotePinings = db.getRepository(ChannelNotePining);
export const RegistryItems = db.getRepository(RegistryItem);
export const Ads = db.getRepository(Ad);
export const PasswordResetRequests = db.getRepository(PasswordResetRequest);

View file

@ -1,14 +1,13 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { AbuseUserReport } from '@/models/entities/abuse-user-report.js';
import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(AbuseUserReport)
export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
public async pack(
export const AbuseUserReportRepository = db.getRepository(AbuseUserReport).extend({
async pack(
src: AbuseUserReport['id'] | AbuseUserReport,
) {
const report = typeof src === 'object' ? src : await this.findOneOrFail(src);
const report = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: report.id,
@ -29,11 +28,11 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
}) : null,
forwarded: report.forwarded,
});
}
},
public packMany(
packMany(
reports: any[],
) {
return Promise.all(reports.map(x => this.pack(x)));
}
}
},
});

View file

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Antenna } from '@/models/entities/antenna.js';
import { Packed } from '@/misc/schema.js';
import { AntennaNotes, UserGroupJoinings } from '../index.js';
@EntityRepository(Antenna)
export class AntennaRepository extends Repository<Antenna> {
public async pack(
export const AntennaRepository = db.getRepository(Antenna).extend({
async pack(
src: Antenna['id'] | Antenna,
): Promise<Packed<'Antenna'>> {
const antenna = typeof src === 'object' ? src : await this.findOneOrFail(src);
const antenna = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const hasUnreadNote = (await AntennaNotes.findOne({ antennaId: antenna.id, read: false })) != null;
const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOne(antenna.userGroupJoiningId) : null;
const hasUnreadNote = (await AntennaNotes.findOneBy({ antennaId: antenna.id, read: false })) != null;
const userGroupJoining = antenna.userGroupJoiningId ? await UserGroupJoinings.findOneBy({ id: antenna.userGroupJoiningId }) : null;
return {
id: antenna.id,
@ -29,5 +28,5 @@ export class AntennaRepository extends Repository<Antenna> {
withFile: antenna.withFile,
hasUnreadNote,
};
}
}
},
});

View file

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { App } from '@/models/entities/app.js';
import { AccessTokens } from '../index.js';
import { Packed } from '@/misc/schema.js';
import { User } from '../entities/user.js';
@EntityRepository(App)
export class AppRepository extends Repository<App> {
public async pack(
export const AppRepository = db.getRepository(App).extend({
async pack(
src: App['id'] | App,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -21,7 +20,7 @@ export class AppRepository extends Repository<App> {
includeProfileImageIds: false,
}, options);
const app = typeof src === 'object' ? src : await this.findOneOrFail(src);
const app = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: app.id,
@ -30,11 +29,11 @@ export class AppRepository extends Repository<App> {
permission: app.permission,
...(opts.includeSecret ? { secret: app.secret } : {}),
...(me ? {
isAuthorized: await AccessTokens.count({
isAuthorized: await AccessTokens.countBy({
appId: app.id,
userId: me.id,
}).then(count => count > 0),
} : {}),
};
}
}
},
});

View file

@ -1,21 +1,20 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Apps } from '../index.js';
import { AuthSession } from '@/models/entities/auth-session.js';
import { awaitAll } from '@/prelude/await-all.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(AuthSession)
export class AuthSessionRepository extends Repository<AuthSession> {
public async pack(
export const AuthSessionRepository = db.getRepository(AuthSession).extend({
async pack(
src: AuthSession['id'] | AuthSession,
me?: { id: User['id'] } | null | undefined
) {
const session = typeof src === 'object' ? src : await this.findOneOrFail(src);
const session = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: session.id,
app: Apps.pack(session.appId, me),
token: session.token,
});
}
}
},
});

View file

@ -1,17 +1,16 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { Blocking } from '@/models/entities/blocking.js';
import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Blocking)
export class BlockingRepository extends Repository<Blocking> {
public async pack(
export const BlockingRepository = db.getRepository(Blocking).extend({
async pack(
src: Blocking['id'] | Blocking,
me?: { id: User['id'] } | null | undefined
): Promise<Packed<'Blocking'>> {
const blocking = typeof src === 'object' ? src : await this.findOneOrFail(src);
const blocking = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: blocking.id,
@ -21,12 +20,12 @@ export class BlockingRepository extends Repository<Blocking> {
detail: true,
}),
});
}
},
public packMany(
packMany(
blockings: any[],
me: { id: User['id'] }
) {
return Promise.all(blockings.map(x => this.pack(x, me)));
}
}
},
});

View file

@ -1,23 +1,22 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Channel } from '@/models/entities/channel.js';
import { Packed } from '@/misc/schema.js';
import { DriveFiles, ChannelFollowings, NoteUnreads } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Channel)
export class ChannelRepository extends Repository<Channel> {
public async pack(
export const ChannelRepository = db.getRepository(Channel).extend({
async pack(
src: Channel['id'] | Channel,
me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Channel'>> {
const channel = typeof src === 'object' ? src : await this.findOneOrFail(src);
const channel = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const meId = me ? me.id : null;
const banner = channel.bannerId ? await DriveFiles.findOne(channel.bannerId) : null;
const banner = channel.bannerId ? await DriveFiles.findOneBy({ id: channel.bannerId }) : null;
const hasUnreadNote = meId ? (await NoteUnreads.findOne({ noteChannelId: channel.id, userId: meId })) != null : undefined;
const hasUnreadNote = meId ? (await NoteUnreads.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined;
const following = meId ? await ChannelFollowings.findOne({
const following = meId ? await ChannelFollowings.findOneBy({
followerId: meId,
followeeId: channel.id,
}) : null;
@ -38,5 +37,5 @@ export class ChannelRepository extends Repository<Channel> {
hasUnreadNote,
} : {}),
};
}
}
},
});

View file

@ -1,15 +1,14 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Clip } from '@/models/entities/clip.js';
import { Packed } from '@/misc/schema.js';
import { Users } from '../index.js';
import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(Clip)
export class ClipRepository extends Repository<Clip> {
public async pack(
export const ClipRepository = db.getRepository(Clip).extend({
async pack(
src: Clip['id'] | Clip,
): Promise<Packed<'Clip'>> {
const clip = typeof src === 'object' ? src : await this.findOneOrFail(src);
const clip = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: clip.id,
@ -20,12 +19,12 @@ export class ClipRepository extends Repository<Clip> {
description: clip.description,
isPublic: clip.isPublic,
});
}
},
public packMany(
packMany(
clips: Clip[],
) {
return Promise.all(clips.map(x => this.pack(x)));
}
}
},
});

View file

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { Users, DriveFolders } from '../index.js';
import { User } from '@/models/entities/user.js';
@ -16,9 +16,8 @@ type PackOptions = {
withUser?: boolean,
};
@EntityRepository(DriveFile)
export class DriveFileRepository extends Repository<DriveFile> {
public validateFileName(name: string): boolean {
export const DriveFileRepository = db.getRepository(DriveFile).extend({
validateFileName(name: string): boolean {
return (
(name.trim().length > 0) &&
(name.length <= 200) &&
@ -26,9 +25,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
(name.indexOf('/') === -1) &&
(name.indexOf('..') === -1)
);
}
},
public getPublicProperties(file: DriveFile): DriveFile['properties'] {
getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) {
const properties = JSON.parse(JSON.stringify(file.properties));
if (file.properties.orientation >= 5) {
@ -39,9 +38,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
}
return file.properties;
}
},
public getPublicUrl(file: DriveFile, thumbnail = false): string | null {
getPublicUrl(file: DriveFile, thumbnail = false): string | null {
// リモートかつメディアプロキシ
if (file.uri != null && file.userHost != null && config.mediaProxy != null) {
return appendQuery(config.mediaProxy, query({
@ -62,9 +61,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'].includes(file.type);
return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url);
}
},
public async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise<number> {
async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise<number> {
const id = typeof user === 'object' ? user.id : user;
const { sum } = await this
@ -75,9 +74,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async calcDriveUsageOfHost(host: string): Promise<number> {
async calcDriveUsageOfHost(host: string): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost = :host', { host: toPuny(host) })
@ -86,9 +85,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async calcDriveUsageOfLocal(): Promise<number> {
async calcDriveUsageOfLocal(): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost IS NULL')
@ -97,9 +96,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async calcDriveUsageOfRemote(): Promise<number> {
async calcDriveUsageOfRemote(): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost IS NOT NULL')
@ -108,11 +107,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
.getRawOne();
return parseInt(sum, 10) || 0;
}
},
public async pack(src: DriveFile['id'], options?: PackOptions): Promise<Packed<'DriveFile'> | null>;
public async pack(src: DriveFile, options?: PackOptions): Promise<Packed<'DriveFile'>>;
public async pack(
async pack(
src: DriveFile['id'] | DriveFile,
options?: PackOptions
): Promise<Packed<'DriveFile'> | null> {
@ -121,11 +118,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
self: false,
}, options);
const file = typeof src === 'object' ? src : await this.findOne(src);
const file = typeof src === 'object' ? src : await this.findOneBy({ id: src });
if (file == null) return null;
const meta = await fetchMeta();
return await awaitAll<Packed<'DriveFile'>>({
id: file.id,
createdAt: file.createdAt.toISOString(),
@ -146,13 +141,13 @@ export class DriveFileRepository extends Repository<DriveFile> {
userId: opts.withUser ? file.userId : null,
user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null,
});
}
},
public async packMany(
async packMany(
files: (DriveFile['id'] | DriveFile)[],
options?: PackOptions
) {
const items = await Promise.all(files.map(f => this.pack(f, options)));
return items.filter(x => x != null);
}
}
},
});

View file

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { DriveFolders, DriveFiles } from '../index.js';
import { DriveFolder } from '@/models/entities/drive-folder.js';
import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(DriveFolder)
export class DriveFolderRepository extends Repository<DriveFolder> {
public async pack(
export const DriveFolderRepository = db.getRepository(DriveFolder).extend({
async pack(
src: DriveFolder['id'] | DriveFolder,
options?: {
detail: boolean
@ -16,7 +15,7 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
detail: false,
}, options);
const folder = typeof src === 'object' ? src : await this.findOneOrFail(src);
const folder = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: folder.id,
@ -25,10 +24,10 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
parentId: folder.parentId,
...(opts.detail ? {
foldersCount: DriveFolders.count({
foldersCount: DriveFolders.countBy({
parentId: folder.id,
}),
filesCount: DriveFiles.count({
filesCount: DriveFiles.countBy({
folderId: folder.id,
}),
@ -39,5 +38,5 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
} : {}),
} : {}),
});
}
}
},
});

View file

@ -1,13 +1,12 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Emoji } from '@/models/entities/emoji.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(Emoji)
export class EmojiRepository extends Repository<Emoji> {
public async pack(
export const EmojiRepository = db.getRepository(Emoji).extend({
async pack(
src: Emoji['id'] | Emoji,
): Promise<Packed<'Emoji'>> {
const emoji = typeof src === 'object' ? src : await this.findOneOrFail(src);
const emoji = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: emoji.id,
@ -18,11 +17,11 @@ export class EmojiRepository extends Repository<Emoji> {
// || emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl || emoji.originalUrl,
};
}
},
public packMany(
packMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.pack(x)));
}
}
},
});

View file

@ -1,20 +1,19 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { FollowRequest } from '@/models/entities/follow-request.js';
import { Users } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(FollowRequest)
export class FollowRequestRepository extends Repository<FollowRequest> {
public async pack(
export const FollowRequestRepository = db.getRepository(FollowRequest).extend({
async pack(
src: FollowRequest['id'] | FollowRequest,
me?: { id: User['id'] } | null | undefined
) {
const request = typeof src === 'object' ? src : await this.findOneOrFail(src);
const request = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: request.id,
follower: await Users.pack(request.followerId, me),
followee: await Users.pack(request.followeeId, me),
};
}
}
},
});

View file

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { Following } from '@/models/entities/following.js';
import { awaitAll } from '@/prelude/await-all.js';
@ -29,25 +29,24 @@ type RemoteFolloweeFollowing = Following & {
followeeSharedInbox: string;
};
@EntityRepository(Following)
export class FollowingRepository extends Repository<Following> {
public isLocalFollower(following: Following): following is LocalFollowerFollowing {
export const FollowingRepository = db.getRepository(Following).extend({
isLocalFollower(following: Following): following is LocalFollowerFollowing {
return following.followerHost == null;
}
},
public isRemoteFollower(following: Following): following is RemoteFollowerFollowing {
isRemoteFollower(following: Following): following is RemoteFollowerFollowing {
return following.followerHost != null;
}
},
public isLocalFollowee(following: Following): following is LocalFolloweeFollowing {
isLocalFollowee(following: Following): following is LocalFolloweeFollowing {
return following.followeeHost == null;
}
},
public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing {
isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing {
return following.followeeHost != null;
}
},
public async pack(
async pack(
src: Following['id'] | Following,
me?: { id: User['id'] } | null | undefined,
opts?: {
@ -55,7 +54,7 @@ export class FollowingRepository extends Repository<Following> {
populateFollower?: boolean;
}
): Promise<Packed<'Following'>> {
const following = typeof src === 'object' ? src : await this.findOneOrFail(src);
const following = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
if (opts == null) opts = {};
@ -71,9 +70,9 @@ export class FollowingRepository extends Repository<Following> {
detail: true,
}) : undefined,
});
}
},
public packMany(
packMany(
followings: any[],
me?: { id: User['id'] } | null | undefined,
opts?: {
@ -82,5 +81,5 @@ export class FollowingRepository extends Repository<Following> {
}
) {
return Promise.all(followings.map(x => this.pack(x, me, opts)));
}
}
},
});

View file

@ -1,25 +1,24 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { GalleryLike } from '@/models/entities/gallery-like.js';
import { GalleryPosts } from '../index.js';
@EntityRepository(GalleryLike)
export class GalleryLikeRepository extends Repository<GalleryLike> {
public async pack(
export const GalleryLikeRepository = db.getRepository(GalleryLike).extend({
async pack(
src: GalleryLike['id'] | GalleryLike,
me?: any
) {
const like = typeof src === 'object' ? src : await this.findOneOrFail(src);
const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: like.id,
post: await GalleryPosts.pack(like.post || like.postId, me),
};
}
},
public packMany(
packMany(
likes: any[],
me: any
) {
return Promise.all(likes.map(x => this.pack(x, me)));
}
}
},
});

View file

@ -1,18 +1,17 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { GalleryPost } from '@/models/entities/gallery-post.js';
import { Packed } from '@/misc/schema.js';
import { Users, DriveFiles, GalleryLikes } from '../index.js';
import { awaitAll } from '@/prelude/await-all.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(GalleryPost)
export class GalleryPostRepository extends Repository<GalleryPost> {
public async pack(
export const GalleryPostRepository = db.getRepository(GalleryPost).extend({
async pack(
src: GalleryPost['id'] | GalleryPost,
me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'GalleryPost'>> {
const meId = me ? me.id : null;
const post = typeof src === 'object' ? src : await this.findOneOrFail(src);
const post = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: post.id,
@ -27,14 +26,14 @@ export class GalleryPostRepository extends Repository<GalleryPost> {
tags: post.tags.length > 0 ? post.tags : undefined,
isSensitive: post.isSensitive,
likedCount: post.likedCount,
isLiked: meId ? await GalleryLikes.findOne({ postId: post.id, userId: meId }).then(x => x != null) : undefined,
isLiked: meId ? await GalleryLikes.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined,
});
}
},
public packMany(
packMany(
posts: GalleryPost[],
me?: { id: User['id'] } | null | undefined,
) {
return Promise.all(posts.map(x => this.pack(x, me)));
}
}
},
});

View file

@ -1,10 +1,9 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Hashtag } from '@/models/entities/hashtag.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(Hashtag)
export class HashtagRepository extends Repository<Hashtag> {
public async pack(
export const HashtagRepository = db.getRepository(Hashtag).extend({
async pack(
src: Hashtag,
): Promise<Packed<'Hashtag'>> {
return {
@ -16,11 +15,11 @@ export class HashtagRepository extends Repository<Hashtag> {
attachedLocalUsersCount: src.attachedLocalUsersCount,
attachedRemoteUsersCount: src.attachedRemoteUsersCount,
};
}
},
public packMany(
packMany(
hashtags: Hashtag[],
) {
return Promise.all(hashtags.map(x => this.pack(x)));
}
}
},
});

View file

@ -1,10 +1,9 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Instance } from '@/models/entities/instance.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(Instance)
export class InstanceRepository extends Repository<Instance> {
public async pack(
export const InstanceRepository = db.getRepository(Instance).extend({
async pack(
instance: Instance,
): Promise<Packed<'FederationInstance'>> {
return {
@ -29,11 +28,11 @@ export class InstanceRepository extends Repository<Instance> {
iconUrl: instance.iconUrl,
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
};
}
},
public packMany(
packMany(
instances: Instance[],
) {
return Promise.all(instances.map(x => this.pack(x)));
}
}
},
});

View file

@ -1,12 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Users, DriveFiles, UserGroups } from '../index.js';
import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(MessagingMessage)
export class MessagingMessageRepository extends Repository<MessagingMessage> {
public async pack(
export const MessagingMessageRepository = db.getRepository(MessagingMessage).extend({
async pack(
src: MessagingMessage['id'] | MessagingMessage,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -19,7 +18,7 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
populateGroup: true,
};
const message = typeof src === 'object' ? src : await this.findOneOrFail(src);
const message = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: message.id,
@ -36,5 +35,5 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
isRead: message.isRead,
reads: message.reads,
};
}
}
},
});

View file

@ -1,14 +1,13 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { ModerationLog } from '@/models/entities/moderation-log.js';
import { awaitAll } from '@/prelude/await-all.js';
@EntityRepository(ModerationLog)
export class ModerationLogRepository extends Repository<ModerationLog> {
public async pack(
export const ModerationLogRepository = db.getRepository(ModerationLog).extend({
async pack(
src: ModerationLog['id'] | ModerationLog,
) {
const log = typeof src === 'object' ? src : await this.findOneOrFail(src);
const log = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: log.id,
@ -20,11 +19,11 @@ export class ModerationLogRepository extends Repository<ModerationLog> {
detail: true,
}),
});
}
},
public packMany(
packMany(
reports: any[],
) {
return Promise.all(reports.map(x => this.pack(x)));
}
}
},
});

View file

@ -1,32 +1,32 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Users } from '../index.js';
import { Muting } from '@/models/entities/muting.js';
import { awaitAll } from '@/prelude/await-all.js';
import { Packed } from '@/misc/schema.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Muting)
export class MutingRepository extends Repository<Muting> {
public async pack(
export const MutingRepository = db.getRepository(Muting).extend({
async pack(
src: Muting['id'] | Muting,
me?: { id: User['id'] } | null | undefined
): Promise<Packed<'Muting'>> {
const muting = typeof src === 'object' ? src : await this.findOneOrFail(src);
const muting = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return await awaitAll({
id: muting.id,
createdAt: muting.createdAt.toISOString(),
expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
muteeId: muting.muteeId,
mutee: Users.pack(muting.muteeId, me, {
detail: true,
}),
});
}
},
public packMany(
packMany(
mutings: any[],
me: { id: User['id'] }
) {
return Promise.all(mutings.map(x => this.pack(x, me)));
}
}
},
});

View file

@ -1,15 +1,14 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { NoteFavorite } from '@/models/entities/note-favorite.js';
import { Notes } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(NoteFavorite)
export class NoteFavoriteRepository extends Repository<NoteFavorite> {
public async pack(
export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
async pack(
src: NoteFavorite['id'] | NoteFavorite,
me?: { id: User['id'] } | null | undefined
) {
const favorite = typeof src === 'object' ? src : await this.findOneOrFail(src);
const favorite = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: favorite.id,
@ -17,12 +16,12 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
noteId: favorite.noteId,
note: await Notes.pack(favorite.note || favorite.noteId, me),
};
}
},
public packMany(
packMany(
favorites: any[],
me: { id: User['id'] }
) {
return Promise.all(favorites.map(x => this.pack(x, me)));
}
}
},
});

View file

@ -1,13 +1,12 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { NoteReaction } from '@/models/entities/note-reaction.js';
import { Notes, Users } from '../index.js';
import { Packed } from '@/misc/schema.js';
import { convertLegacyReaction } from '@/misc/reaction-lib.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(NoteReaction)
export class NoteReactionRepository extends Repository<NoteReaction> {
public async pack(
export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
async pack(
src: NoteReaction['id'] | NoteReaction,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -18,7 +17,7 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
withNote: false,
}, options);
const reaction = typeof src === 'object' ? src : await this.findOneOrFail(src);
const reaction = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: reaction.id,
@ -29,5 +28,5 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
note: await Notes.pack(reaction.note ?? reaction.noteId, me),
} : {}),
};
}
}
},
});

View file

@ -1,4 +1,4 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { In } from 'typeorm';
import * as mfm from 'mfm-js';
import { Note } from '@/models/entities/note.js';
import { User } from '@/models/entities/user.js';
@ -9,10 +9,133 @@ import { awaitAll } from '@/prelude/await-all.js';
import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@/misc/reaction-lib.js';
import { NoteReaction } from '@/models/entities/note-reaction.js';
import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { db } from '@/db/postgre.js';
@EntityRepository(Note)
export class NoteRepository extends Repository<Note> {
public async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
hide = false;
} else {
hide = true;
}
}
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility === 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション
hide = false;
} else {
// フォロワーかどうか
const following = await Followings.findOneBy({
followeeId: packedNote.userId,
followerId: meId,
});
if (following == null) {
hide = true;
} else {
hide = false;
}
}
}
if (hide) {
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.isHidden = true;
}
}
async function populatePoll(note: Note, meId: User['id'] | null) {
const poll = await Polls.findOneByOrFail({ noteId: note.id });
const choices = poll.choices.map(c => ({
text: c,
votes: poll.votes[poll.choices.indexOf(c)],
isVoted: false,
}));
if (meId) {
if (poll.multiple) {
const votes = await PollVotes.findBy({
userId: meId,
noteId: note.id,
});
const myChoices = votes.map(v => v.choice);
for (const myChoice of myChoices) {
choices[myChoice].isVoted = true;
}
} else {
const vote = await PollVotes.findOneBy({
userId: meId,
noteId: note.id,
});
if (vote) {
choices[vote.choice].isVoted = true;
}
}
}
return {
multiple: poll.multiple,
expiresAt: poll.expiresAt,
choices,
};
}
async function populateMyReaction(note: Note, meId: User['id'], _hint_?: {
myReactions: Map<Note['id'], NoteReaction | null>;
}) {
if (_hint_?.myReactions) {
const reaction = _hint_.myReactions.get(note.id);
if (reaction) {
return convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
return undefined;
}
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
const reaction = await NoteReactions.findOneBy({
userId: meId,
noteId: note.id,
});
if (reaction) {
return convertLegacyReaction(reaction.reaction);
}
return undefined;
}
export const NoteRepository = db.getRepository(Note).extend({
async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
// visibility が specified かつ自分が指定されていなかったら非表示
if (note.visibility === 'specified') {
if (meId == null) {
@ -45,7 +168,7 @@ export class NoteRepository extends Repository<Note> {
return true;
} else {
// フォロワーかどうか
const following = await Followings.findOne({
const following = await Followings.findOneBy({
followeeId: note.userId,
followerId: meId,
});
@ -59,69 +182,9 @@ export class NoteRepository extends Repository<Note> {
}
return true;
}
},
private async hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
hide = false;
} else {
hide = true;
}
}
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility === 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション
hide = false;
} else {
// フォロワーかどうか
const following = await Followings.findOne({
followeeId: packedNote.userId,
followerId: meId,
});
if (following == null) {
hide = true;
} else {
hide = false;
}
}
}
if (hide) {
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.isHidden = true;
}
}
public async pack(
async pack(
src: Note['id'] | Note,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -138,68 +201,9 @@ export class NoteRepository extends Repository<Note> {
}, options);
const meId = me ? me.id : null;
const note = typeof src === 'object' ? src : await this.findOneOrFail(src);
const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const host = note.userHost;
async function populatePoll() {
const poll = await Polls.findOneOrFail(note.id);
const choices = poll.choices.map(c => ({
text: c,
votes: poll.votes[poll.choices.indexOf(c)],
isVoted: false,
}));
if (poll.multiple) {
const votes = await PollVotes.find({
userId: meId!,
noteId: note.id,
});
const myChoices = votes.map(v => v.choice);
for (const myChoice of myChoices) {
choices[myChoice].isVoted = true;
}
} else {
const vote = await PollVotes.findOne({
userId: meId!,
noteId: note.id,
});
if (vote) {
choices[vote.choice].isVoted = true;
}
}
return {
multiple: poll.multiple,
expiresAt: poll.expiresAt,
choices,
};
}
async function populateMyReaction() {
if (options?._hint_?.myReactions) {
const reaction = options._hint_.myReactions.get(note.id);
if (reaction) {
return convertLegacyReaction(reaction.reaction);
} else if (reaction === null) {
return undefined;
}
// 実装上抜けがあるだけかもしれないので、「ヒントに含まれてなかったら(=undefinedなら)return」のようにはしない
}
const reaction = await NoteReactions.findOne({
userId: meId!,
noteId: note.id,
});
if (reaction) {
return convertLegacyReaction(reaction.reaction);
}
return undefined;
}
let text = note.text;
if (note.name && (note.url ?? note.uri)) {
@ -209,7 +213,7 @@ export class NoteRepository extends Repository<Note> {
const channel = note.channelId
? note.channel
? note.channel
: await Channels.findOne(note.channelId)
: await Channels.findOneBy({ id: note.channelId })
: null;
const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, ''));
@ -255,10 +259,10 @@ export class NoteRepository extends Repository<Note> {
_hint_: options?._hint_,
}) : undefined,
poll: note.hasPoll ? populatePoll() : undefined,
poll: note.hasPoll ? populatePoll(note, meId) : undefined,
...(meId ? {
myReaction: populateMyReaction(),
myReaction: populateMyReaction(note, meId, options?._hint_),
} : {}),
} : {}),
});
@ -275,13 +279,13 @@ export class NoteRepository extends Repository<Note> {
}
if (!opts.skipHide) {
await this.hideNote(packed, meId);
await hideNote(packed, meId);
}
return packed;
}
},
public async packMany(
async packMany(
notes: Note[],
me?: { id: User['id'] } | null | undefined,
options?: {
@ -296,7 +300,7 @@ export class NoteRepository extends Repository<Note> {
if (meId) {
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
const targets = [...notes.map(n => n.id), ...renoteIds];
const myReactions = await NoteReactions.find({
const myReactions = await NoteReactions.findBy({
userId: meId,
noteId: In(targets),
});
@ -314,5 +318,5 @@ export class NoteRepository extends Repository<Note> {
myReactions: myReactionsMap,
},
})));
}
}
},
});

View file

@ -1,4 +1,4 @@
import { EntityRepository, In, Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { Users, Notes, UserGroupInvitations, AccessTokens, NoteReactions } from '../index.js';
import { Notification } from '@/models/entities/notification.js';
import { awaitAll } from '@/prelude/await-all.js';
@ -8,10 +8,10 @@ import { NoteReaction } from '@/models/entities/note-reaction.js';
import { User } from '@/models/entities/user.js';
import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { notificationTypes } from '@/types.js';
import { db } from '@/db/postgre.js';
@EntityRepository(Notification)
export class NotificationRepository extends Repository<Notification> {
public async pack(
export const NotificationRepository = db.getRepository(Notification).extend({
async pack(
src: Notification['id'] | Notification,
options: {
_hintForEachNotes_?: {
@ -19,8 +19,8 @@ export class NotificationRepository extends Repository<Notification> {
};
}
): Promise<Packed<'Notification'>> {
const notification = typeof src === 'object' ? src : await this.findOneOrFail(src);
const token = notification.appAccessTokenId ? await AccessTokens.findOneOrFail(notification.appAccessTokenId) : null;
const notification = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const token = notification.appAccessTokenId ? await AccessTokens.findOneByOrFail({ id: notification.appAccessTokenId }) : null;
return await awaitAll({
id: notification.id,
@ -67,6 +67,12 @@ export class NotificationRepository extends Repository<Notification> {
}),
choice: notification.choice,
} : {}),
...(notification.type === 'pollEnded' ? {
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
detail: true,
_hint_: options._hintForEachNotes_,
}),
} : {}),
...(notification.type === 'groupInvited' ? {
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
} : {}),
@ -76,9 +82,9 @@ export class NotificationRepository extends Repository<Notification> {
icon: notification.customIcon || token?.iconUrl,
} : {}),
});
}
},
public async packMany(
async packMany(
notifications: Notification[],
meId: User['id']
) {
@ -89,7 +95,7 @@ export class NotificationRepository extends Repository<Notification> {
const myReactionsMap = new Map<Note['id'], NoteReaction | null>();
const renoteIds = notes.filter(n => n.renoteId != null).map(n => n.renoteId!);
const targets = [...noteIds, ...renoteIds];
const myReactions = await NoteReactions.find({
const myReactions = await NoteReactions.findBy({
userId: meId,
noteId: In(targets),
});
@ -105,5 +111,5 @@ export class NotificationRepository extends Repository<Notification> {
myReactions: myReactionsMap,
},
})));
}
}
},
});

View file

@ -1,26 +1,25 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { PageLike } from '@/models/entities/page-like.js';
import { Pages } from '../index.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(PageLike)
export class PageLikeRepository extends Repository<PageLike> {
public async pack(
export const PageLikeRepository = db.getRepository(PageLike).extend({
async pack(
src: PageLike['id'] | PageLike,
me?: { id: User['id'] } | null | undefined
) {
const like = typeof src === 'object' ? src : await this.findOneOrFail(src);
const like = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: like.id,
page: await Pages.pack(like.page || like.pageId, me),
};
}
},
public packMany(
packMany(
likes: any[],
me: { id: User['id'] }
) {
return Promise.all(likes.map(x => this.pack(x, me)));
}
}
},
});

View file

@ -1,4 +1,4 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Page } from '@/models/entities/page.js';
import { Packed } from '@/misc/schema.js';
import { Users, DriveFiles, PageLikes } from '../index.js';
@ -6,20 +6,19 @@ import { awaitAll } from '@/prelude/await-all.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { User } from '@/models/entities/user.js';
@EntityRepository(Page)
export class PageRepository extends Repository<Page> {
public async pack(
export const PageRepository = db.getRepository(Page).extend({
async pack(
src: Page['id'] | Page,
me?: { id: User['id'] } | null | undefined,
): Promise<Packed<'Page'>> {
const meId = me ? me.id : null;
const page = typeof src === 'object' ? src : await this.findOneOrFail(src);
const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const attachedFiles: Promise<DriveFile | undefined>[] = [];
const collectFile = (xs: any[]) => {
for (const x of xs) {
if (x.type === 'image') {
attachedFiles.push(DriveFiles.findOne({
attachedFiles.push(DriveFiles.findOneBy({
id: x.fileId,
userId: page.userId,
}));
@ -76,14 +75,14 @@ export class PageRepository extends Repository<Page> {
eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
likedCount: page.likedCount,
isLiked: meId ? await PageLikes.findOne({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
});
}
},
public packMany(
packMany(
pages: Page[],
me?: { id: User['id'] } | null | undefined,
) {
return Promise.all(pages.map(x => this.pack(x, me)));
}
}
},
});

View file

@ -1,6 +1,5 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Relay } from '@/models/entities/relay.js';
@EntityRepository(Relay)
export class RelayRepository extends Repository<Relay> {
}
export const RelayRepository = db.getRepository(Relay).extend({
});

View file

@ -1,11 +1,10 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { Signin } from '@/models/entities/signin.js';
@EntityRepository(Signin)
export class SigninRepository extends Repository<Signin> {
public async pack(
export const SigninRepository = db.getRepository(Signin).extend({
async pack(
src: Signin,
) {
return src;
}
}
},
});

View file

@ -1,23 +1,22 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js';
import { UserGroups } from '../index.js';
@EntityRepository(UserGroupInvitation)
export class UserGroupInvitationRepository extends Repository<UserGroupInvitation> {
public async pack(
export const UserGroupInvitationRepository = db.getRepository(UserGroupInvitation).extend({
async pack(
src: UserGroupInvitation['id'] | UserGroupInvitation,
) {
const invitation = typeof src === 'object' ? src : await this.findOneOrFail(src);
const invitation = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
return {
id: invitation.id,
group: await UserGroups.pack(invitation.userGroup || invitation.userGroupId),
};
}
},
public packMany(
packMany(
invitations: any[],
) {
return Promise.all(invitations.map(x => this.pack(x)));
}
}
},
});

View file

@ -1,16 +1,15 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { UserGroup } from '@/models/entities/user-group.js';
import { UserGroupJoinings } from '../index.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(UserGroup)
export class UserGroupRepository extends Repository<UserGroup> {
public async pack(
export const UserGroupRepository = db.getRepository(UserGroup).extend({
async pack(
src: UserGroup['id'] | UserGroup,
): Promise<Packed<'UserGroup'>> {
const userGroup = typeof src === 'object' ? src : await this.findOneOrFail(src);
const userGroup = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const users = await UserGroupJoinings.find({
const users = await UserGroupJoinings.findBy({
userGroupId: userGroup.id,
});
@ -21,5 +20,5 @@ export class UserGroupRepository extends Repository<UserGroup> {
ownerId: userGroup.userId,
userIds: users.map(x => x.userId),
};
}
}
},
});

View file

@ -1,16 +1,15 @@
import { EntityRepository, Repository } from 'typeorm';
import { db } from '@/db/postgre.js';
import { UserList } from '@/models/entities/user-list.js';
import { UserListJoinings } from '../index.js';
import { Packed } from '@/misc/schema.js';
@EntityRepository(UserList)
export class UserListRepository extends Repository<UserList> {
public async pack(
export const UserListRepository = db.getRepository(UserList).extend({
async pack(
src: UserList['id'] | UserList,
): Promise<Packed<'UserList'>> {
const userList = typeof src === 'object' ? src : await this.findOneOrFail(src);
const userList = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const users = await UserListJoinings.find({
const users = await UserListJoinings.findBy({
userListId: userList.id,
});
@ -20,5 +19,5 @@ export class UserListRepository extends Repository<UserList> {
name: userList.name,
userIds: users.map(x => x.userId),
};
}
}
},
});

View file

@ -8,6 +8,11 @@ import { awaitAll, Promiseable } from '@/prelude/await-all.js';
import { populateEmojis } from '@/misc/populate-emojis.js';
import { getAntennas } from '@/misc/antenna-cache.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
import { Cache } from '@/misc/cache.js';
import { Instance } from '../entities/instance.js';
import { db } from '@/db/postgre.js';
const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
@ -19,51 +24,69 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
const ajv = new Ajv();
@EntityRepository(User)
export class UserRepository extends Repository<User> {
public localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
public passwordSchema = { type: 'string', minLength: 1 } as const;
public nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
public descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
public locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
public birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
const passwordSchema = { type: 'string', minLength: 1 } as const;
const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
const descriptionSchema = { type: 'string', minLength: 1, maxLength: 500 } as const;
const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
function isLocalUser(user: User): user is ILocalUser;
function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
function isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null;
}
function isRemoteUser(user: User): user is IRemoteUser;
function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user);
}
export const UserRepository = db.getRepository(User).extend({
localUsernameSchema,
passwordSchema,
nameSchema,
descriptionSchema,
locationSchema,
birthdaySchema,
//#region Validators
public validateLocalUsername = ajv.compile(this.localUsernameSchema);
public validatePassword = ajv.compile(this.passwordSchema);
public validateName = ajv.compile(this.nameSchema);
public validateDescription = ajv.compile(this.descriptionSchema);
public validateLocation = ajv.compile(this.locationSchema);
public validateBirthday = ajv.compile(this.birthdaySchema);
validateLocalUsername: ajv.compile(localUsernameSchema),
validatePassword: ajv.compile(passwordSchema),
validateName: ajv.compile(nameSchema),
validateDescription: ajv.compile(descriptionSchema),
validateLocation: ajv.compile(locationSchema),
validateBirthday: ajv.compile(birthdaySchema),
//#endregion
public async getRelation(me: User['id'], target: User['id']) {
async getRelation(me: User['id'], target: User['id']) {
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
Followings.findOne({
Followings.findOneBy({
followerId: me,
followeeId: target,
}),
Followings.findOne({
Followings.findOneBy({
followerId: target,
followeeId: me,
}),
FollowRequests.findOne({
FollowRequests.findOneBy({
followerId: me,
followeeId: target,
}),
FollowRequests.findOne({
FollowRequests.findOneBy({
followerId: target,
followeeId: me,
}),
Blockings.findOne({
Blockings.findOneBy({
blockerId: me,
blockeeId: target,
}),
Blockings.findOne({
Blockings.findOneBy({
blockerId: target,
blockeeId: me,
}),
Mutings.findOne({
Mutings.findOneBy({
muterId: me,
muteeId: target,
}),
@ -79,14 +102,14 @@ export class UserRepository extends Repository<User> {
isBlocked: fromBlocked != null,
isMuted: mute != null,
};
}
},
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({
async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
const mute = await Mutings.findBy({
muterId: userId,
});
const joinings = await UserGroupJoinings.find({ userId: userId });
const joinings = await UserGroupJoinings.findBy({ userId: userId });
const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
.where(`message.groupId = :groupId`, { groupId: j.userGroupId })
@ -108,44 +131,44 @@ export class UserRepository extends Repository<User> {
]);
return withUser || withGroups.some(x => x);
}
},
public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
const reads = await AnnouncementReads.find({
async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
const reads = await AnnouncementReads.findBy({
userId: userId,
});
const count = await Announcements.count(reads.length > 0 ? {
const count = await Announcements.countBy(reads.length > 0 ? {
id: Not(In(reads.map(read => read.announcementId))),
} : {});
return count > 0;
}
},
public async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
async getHasUnreadAntenna(userId: User['id']): Promise<boolean> {
const myAntennas = (await getAntennas()).filter(a => a.userId === userId);
const unread = myAntennas.length > 0 ? await AntennaNotes.findOne({
const unread = myAntennas.length > 0 ? await AntennaNotes.findOneBy({
antennaId: In(myAntennas.map(x => x.id)),
read: false,
}) : null;
return unread != null;
}
},
public async getHasUnreadChannel(userId: User['id']): Promise<boolean> {
const channels = await ChannelFollowings.find({ followerId: userId });
async getHasUnreadChannel(userId: User['id']): Promise<boolean> {
const channels = await ChannelFollowings.findBy({ followerId: userId });
const unread = channels.length > 0 ? await NoteUnreads.findOne({
const unread = channels.length > 0 ? await NoteUnreads.findOneBy({
userId: userId,
noteChannelId: In(channels.map(x => x.followeeId)),
}) : null;
return unread != null;
}
},
public async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({
async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
const mute = await Mutings.findBy({
muterId: userId,
});
const mutedUserIds = mute.map(m => m.muteeId);
@ -160,17 +183,17 @@ export class UserRepository extends Repository<User> {
});
return count > 0;
}
},
public async getHasPendingReceivedFollowRequest(userId: User['id']): Promise<boolean> {
const count = await FollowRequests.count({
async getHasPendingReceivedFollowRequest(userId: User['id']): Promise<boolean> {
const count = await FollowRequests.countBy({
followeeId: userId,
});
return count > 0;
}
},
public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' {
getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' {
if (user.hideOnlineStatus) return 'unknown';
if (user.lastActiveDate == null) return 'unknown';
const elapsed = Date.now() - user.lastActiveDate.getTime();
@ -179,22 +202,22 @@ export class UserRepository extends Repository<User> {
elapsed < USER_ACTIVE_THRESHOLD ? 'active' :
'offline'
);
}
},
public getAvatarUrl(user: User): string {
getAvatarUrl(user: User): string {
// TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング
if (user.avatar) {
return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
} else {
return this.getIdenticonUrl(user.id);
}
}
},
public getIdenticonUrl(userId: User['id']): string {
getIdenticonUrl(userId: User['id']): string {
return `${config.url}/identicon/${userId}`;
}
},
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User,
me?: { id: User['id'] } | null | undefined,
options?: {
@ -211,11 +234,15 @@ export class UserRepository extends Repository<User> {
if (typeof src === 'object') {
user = src;
if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOne(src.avatarId) ?? null;
if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOne(src.bannerId) ?? null;
if (src.avatar === undefined && src.avatarId) src.avatar = await DriveFiles.findOneBy({ id: src.avatarId }) ?? null;
if (src.banner === undefined && src.bannerId) src.banner = await DriveFiles.findOneBy({ id: src.bannerId }) ?? null;
} else {
user = await this.findOneOrFail(src, {
relations: ['avatar', 'banner'],
user = await this.findOneOrFail({
where: { id: src },
relations: {
avatar: true,
banner: true,
},
});
}
@ -228,7 +255,7 @@ export class UserRepository extends Repository<User> {
.innerJoinAndSelect('pin.note', 'note')
.orderBy('pin.id', 'DESC')
.getMany() : [];
const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null;
const profile = opts.detail ? await UserProfiles.findOneByOrFail({ userId: user.id }) : null;
const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followingCount :
@ -254,8 +281,10 @@ export class UserRepository extends Repository<User> {
isModerator: user.isModerator || falsy,
isBot: user.isBot || falsy,
isCat: user.isCat || falsy,
showTimelineReplies: user.showTimelineReplies || falsy,
instance: user.host ? Instances.findOne({ host: user.host }).then(instance => instance ? {
instance: user.host ? userInstanceCache.fetch(user.host,
() => Instances.findOneBy({ host: user.host! }),
v => v != null
).then(instance => instance ? {
name: instance.name,
softwareName: instance.softwareName,
softwareVersion: instance.softwareVersion,
@ -297,7 +326,7 @@ export class UserRepository extends Repository<User> {
twoFactorEnabled: profile!.twoFactorEnabled,
usePasswordLessLogin: profile!.usePasswordLessLogin,
securityKeys: profile!.twoFactorEnabled
? UserSecurityKeys.count({
? UserSecurityKeys.countBy({
userId: user.id,
}).then(result => result >= 1)
: false,
@ -334,6 +363,7 @@ export class UserRepository extends Repository<User> {
mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: profile!.mutingNotificationTypes,
emailNotificationTypes: profile!.emailNotificationTypes,
showTimelineReplies: user.showTimelineReplies || falsy,
} : {}),
...(opts.includeSecrets ? {
@ -344,7 +374,11 @@ export class UserRepository extends Repository<User> {
where: {
userId: user.id,
},
select: ['id', 'name', 'lastUsed'],
select: {
id: true,
name: true,
lastUsed: true,
},
})
: [],
} : {}),
@ -361,9 +395,9 @@ export class UserRepository extends Repository<User> {
} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
return await awaitAll(packed);
}
},
public packMany<D extends boolean = false>(
packMany<D extends boolean = false>(
users: (User['id'] | User)[],
me?: { id: User['id'] } | null | undefined,
options?: {
@ -372,17 +406,8 @@ export class UserRepository extends Repository<User> {
}
): Promise<IsUserDetailed<D>[]> {
return Promise.all(users.map(u => this.pack(u, me, options)));
}
},
public isLocalUser(user: User): user is ILocalUser;
public isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
public isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null;
}
public isRemoteUser(user: User): user is IRemoteUser;
public isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
public isRemoteUser(user: User | { host: User['host'] }): boolean {
return !this.isLocalUser(user);
}
}
isLocalUser,
isRemoteUser,
});

View file

@ -12,6 +12,11 @@ export const packedMutingSchema = {
optional: false, nullable: false,
format: 'date-time',
},
expiresAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
muteeId: {
type: 'string',
optional: false, nullable: false,

View file

@ -8,10 +8,11 @@ import processInbox from './processors/inbox.js';
import processDb from './processors/db/index.js';
import processObjectStorage from './processors/object-storage/index.js';
import processSystemQueue from './processors/system/index.js';
import { endedPollNotification } from './processors/ended-poll-notification.js';
import { queueLogger } from './logger.js';
import { DriveFile } from '@/models/entities/drive-file.js';
import { getJobInfo } from './get-job-info.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues.js';
import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue, endedPollNotificationQueue } from './queues.js';
import { ThinUser } from './types.js';
import { IActivity } from '@/remote/activitypub/type.js';
@ -255,12 +256,14 @@ export default function() {
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
endedPollNotificationQueue.process(endedPollNotification);
processDb(dbQueue);
processObjectStorage(objectStorageQueue);
systemQueue.add('tickCharts', {
}, {
repeat: { cron: '55 * * * *' },
removeOnComplete: true,
});
systemQueue.add('resyncCharts', {
@ -273,6 +276,12 @@ export default function() {
repeat: { cron: '0 0 * * *' },
});
systemQueue.add('checkExpiredMutings', {
}, {
repeat: { cron: '*/5 * * * *' },
removeOnComplete: true,
});
processSystemQueue(systemQueue);
}

View file

@ -13,7 +13,7 @@ const logger = queueLogger.createSubLogger('delete-account');
export async function deleteAccount(job: Bull.Job<DbUserDeleteJobData>): Promise<string | void> {
logger.info(`Deleting account of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
return;
}
@ -75,7 +75,7 @@ export async function deleteAccount(job: Bull.Job<DbUserDeleteJobData>): Promise
}
{ // Send email notification
const profile = await UserProfiles.findOneOrFail(user.id);
const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
if (profile.email && profile.emailVerified) {
sendEmail(profile.email, 'Account deleted',
`Your account has been deleted.`,

View file

@ -11,7 +11,7 @@ const logger = queueLogger.createSubLogger('delete-drive-files');
export async function deleteDriveFiles(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Deleting drive files of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -44,7 +44,7 @@ export async function deleteDriveFiles(job: Bull.Job<DbUserJobData>, done: any):
deletedCount++;
}
const total = await DriveFiles.count({
const total = await DriveFiles.countBy({
userId: user.id,
});

View file

@ -15,7 +15,7 @@ const logger = queueLogger.createSubLogger('export-blocking');
export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Exporting blocking of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -56,7 +56,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
cursor = blockings[blockings.length - 1].id;
for (const block of blockings) {
const u = await Users.findOne({ id: block.blockeeId });
const u = await Users.findOneBy({ id: block.blockeeId });
if (u == null) {
exportedCount++; continue;
}
@ -75,7 +75,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
exportedCount++;
}
const total = await Blockings.count({
const total = await Blockings.countBy({
blockerId: user.id,
});

View file

@ -12,13 +12,14 @@ import { Users, Emojis } from '@/models/index.js';
import { } from '@/queue/types.js';
import { downloadUrl } from '@/misc/download-url.js';
import config from '@/config/index.js';
import { IsNull } from 'typeorm';
const logger = queueLogger.createSubLogger('export-custom-emojis');
export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promise<void> {
logger.info(`Exporting custom emojis ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -57,7 +58,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
const customEmojis = await Emojis.find({
where: {
host: null,
host: IsNull(),
},
order: {
id: 'ASC',

View file

@ -16,7 +16,7 @@ const logger = queueLogger.createSubLogger('export-following');
export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () => void): Promise<void> {
logger.info(`Exporting following of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -36,7 +36,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () =>
let cursor: Following['id'] | null = null;
const mutings = job.data.excludeMuting ? await Mutings.find({
const mutings = job.data.excludeMuting ? await Mutings.findBy({
muterId: user.id,
}) : [];
@ -60,7 +60,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () =>
cursor = followings[followings.length - 1].id;
for (const following of followings) {
const u = await Users.findOne({ id: following.followeeId });
const u = await Users.findOneBy({ id: following.followeeId });
if (u == null) {
continue;
}

View file

@ -7,7 +7,7 @@ import { addFile } from '@/services/drive/add-file.js';
import { format as dateFormat } from 'date-fns';
import { getFullApAccount } from '@/misc/convert-host.js';
import { Users, Mutings } from '@/models/index.js';
import { MoreThan } from 'typeorm';
import { IsNull, MoreThan } from 'typeorm';
import { DbUserJobData } from '@/queue/types.js';
const logger = queueLogger.createSubLogger('export-mute');
@ -15,7 +15,7 @@ const logger = queueLogger.createSubLogger('export-mute');
export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promise<void> {
logger.info(`Exporting mute of ${job.data.user.id} ...`);
const user = await Users.findOne(job.data.user.id);
const user = await Users.findOneBy({ id: job.data.user.id });
if (user == null) {
done();
return;
@ -40,6 +40,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
const mutes = await Mutings.find({
where: {
muterId: user.id,
expiresAt: IsNull(),
...(cursor ? { id: MoreThan(cursor) } : {}),
},
take: 100,
@ -56,7 +57,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
cursor = mutes[mutes.length - 1].id;
for (const mute of mutes) {
const u = await Users.findOne({ id: mute.muteeId });
const u = await Users.findOneBy({ id: mute.muteeId });
if (u == null) {
exportedCount++; continue;
}
@ -75,7 +76,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
exportedCount++;
}
const total = await Mutings.count({
const total = await Mutings.countBy({
muterId: user.id,
});

Some files were not shown because too many files have changed in this diff Show more