Compare commits

...

59 Commits

Author SHA1 Message Date
tamaina
b6b300a12a fix(frontend): MkModalのスクロールを復活させる 2023-07-26 04:49:58 +00:00
syuilo
e5fce15ba5 fix(frontend): Selecting all emojis in Custom emoji is impossible
Fix #11381
2023-07-26 10:30:52 +09:00
syuilo
c59a9c6f84 New Crowdin updates (#11341)
* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Uzbek)

* New translations ja-JP.yml (Hungarian)
2023-07-26 10:20:40 +09:00
Ibuki Sugiyama
545b5cab96 fix: insert bottom spacing appropriately (#11370) 2023-07-25 19:49:52 +09:00
tamaina
fe13755be8 refactor: Array.from(iterable).map(mapfn)をArray.from(iterable | { length: number }, mapfn)に (#11337)
* refactor: Array.from(iterable).map(mapfn)をArray.from(iterable, mapfn)に

* Update packages/frontend/src/components/MkTagCloud.vue

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

---------

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-07-25 10:46:52 +00:00
Acid Chicken (硫酸鶏)
9208cfd5f3 fix: hide sensitive gallery image on OGP (#11380) 2023-07-25 10:45:46 +00:00
tamaina
81ba841fb8 perf(frontend): MkMediaListのアスペクト比制限を調整 (#11377) 2023-07-25 19:44:52 +09:00
Koki Takahashi
0404d9c103 fix(backend): Change isPublic to correctly recognize as:Public term (#11347)
* fix: Change isPublic to correctly recognize as:Public term

* Update CHANGELOG.md

---------

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-07-25 10:35:21 +00:00
tamaina
c1a19ff900 fix(backend): Use OFFSET instead of SKIP when using LIMIT (#11379)
* fix(backend): Use OFFSET instead of SKIP when using LIMIT

* update CHANGELOG.md
2023-07-25 19:21:50 +09:00
hutchisr
ac6a8edf0b Use BullMQAdapter (#11367) 2023-07-25 16:32:52 +09:00
tamaina
8851e90316 update CHANGELOG.md 2023-07-24 06:22:33 +00:00
tamaina
fca6ae9403 enhance(frontend): リストTLで、ユーザーが追加・削除されてもTLを更新しないように 2023-07-24 06:11:30 +00:00
tamaina
4f4f15a6a4 chore(frontend): MkImgWithBlurhashのデコードのエラーをconsole.logに 2023-07-24 06:05:37 +00:00
syuilo
7097d553e4 13.14.1 2023-07-21 20:38:30 +09:00
syuilo
b8973a6f19 New Crowdin updates (#11336)
* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Swedish)
2023-07-21 20:26:13 +09:00
Acid Chicken (硫酸鶏)
50de2e76b4 test: MkAd play 2023-07-21 11:22:43 +00:00
Acid Chicken (硫酸鶏)
40c5699d93 fix: typo 2023-07-21 10:56:48 +00:00
Acid Chicken (硫酸鶏)
a87dd5ddec fix: response to experimental rIC support on Safari removed due to Google Login bug? 2023-07-21 10:53:37 +00:00
tamaina
561683f097 chore: update pnpm to 8.6.9 2023-07-21 04:04:51 +00:00
Acid Chicken (硫酸鶏)
c9b9db13c7 refactor: avoid redundant method chain 2023-07-21 10:07:03 +00:00
kabo2468
a8cd8ed99e enhance: 自動でたたまれる機能が返信先や引用RNにも適用されるように (#10989)
* 返信、引用RNでMFMがあったら自動で隠すように

* Update CHANGELOG.md

* Update MkSubNoteContent.vue

* refactor: avoid `Boolean`

* docs: update CHANGELOG.md

---------

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-07-21 09:58:57 +00:00
Alex
b3358f0a8b enhance: Add OGP data for notes with multiple images/videos (#11142)
* Add OGP data for notes with multiple images/videos

* Update CHANGELOG.md

* Update packages/backend/src/server/web/views/note.pug

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* Update packages/backend/src/server/web/views/note.pug

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

---------

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
2023-07-21 09:40:30 +00:00
syuilo
af2368bd2b perf(backend): use RSA 2048bit
#11129
2023-07-21 11:59:00 +09:00
syuilo
2cb8e8a748 fix(backend): antennas/notesを叩いてもアンテナの無効化が解除されないことがある問題を修正 2023-07-21 10:09:03 +09:00
syuilo
c40f52b262 Update CHANGELOG.md 2023-07-21 10:07:19 +09:00
meron
2801946226 fix(server): アンテナ再有効の手段にアンテナ設定の更新を追加 (#11036)
* fix(server): アンテナ再有効の手段にアンテナの表示とアンテナ設定の更新を追加

* 無効+Redisも空なアンテナの再有効化手段を antennas/update だけに
2023-07-21 10:06:11 +09:00
woxtu
0d2e3df061 Hide action buttons on MkUrlPreviewPopup (#11332) 2023-07-21 09:57:21 +09:00
Kagami Sascha Rosylight
64c142b1ca chore(backend): better error message for unsupported attestation (#11333) 2023-07-21 07:49:39 +09:00
Kagami Sascha Rosylight
df2b61fcc6 fix(frontend): workaround storybook issue (#11334) 2023-07-21 07:11:32 +09:00
tamaina
509e3f979e fix redis config
https://github.com/misskey-dev/misskey/pull/11329#issuecomment-1644217395
2023-07-20 16:22:47 +00:00
syuilo
b546d4108f 🎨 2023-07-20 20:18:55 +09:00
syuilo
46f86a9146 🎨 2023-07-20 20:17:40 +09:00
tamaina
2191479c76 fix CHANGELOG.md 2023-07-20 10:52:54 +00:00
tamaina
0a06eb27da enhance(backend): 設定ファイルでioredisの全てのオプションを指定可能に (#11329)
* enhance(backend): 設定ファイルでioredisの全てのオプションを指定可能に

* yappa kousuru

* fix

* fix?

* fix

* Revert "fix"

This reverts commit 227f19ff3a.

* fix
2023-07-20 19:50:31 +09:00
Kagami Sascha Rosylight
47b684100d refactor(frontend): use nodemon for storybook (#11297)
* refactor(frontend): use nodemon for storybook

* Update package.json
2023-07-20 17:41:48 +09:00
syuilo
7d6ea74288 enhance(backend): admin/emoji/addで作成した絵文字を返すように 2023-07-20 17:40:04 +09:00
syuilo
8e11a30c36 enhance(frontend): 絵文字登録でファイル名を絵文字名の初期値に 2023-07-20 17:29:21 +09:00
syuilo
5dc8f63a07 Update CHANGELOG.md 2023-07-20 17:03:56 +09:00
Acid Chicken (硫酸鶏)
e6fca72171 perf: use slacc instead of unzipper (#10780)
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
2023-07-20 17:00:54 +09:00
syuilo
d2c942348c 13.14.0-beta.7 2023-07-20 15:50:37 +09:00
syuilo
21277952ca New Crowdin updates (#11323)
* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)
2023-07-20 15:50:14 +09:00
tamaina
dc93a418c0 perf(backend): createPersonでキャッシュに保存する, DBのトランザクション回数を減らす (#11324)
* perf(backend): createPersonでキャッシュを積極的に利用する, トランザクション回数を減らす

* move comment

* fix

* oops

* fix

* fix

* fix
2023-07-20 14:44:37 +09:00
syuilo
51b02ef23e Update pnpm-lock.yaml 2023-07-20 12:45:43 +09:00
syuilo
32430a0dba update aiscript to 0.15.0 2023-07-20 12:43:58 +09:00
syuilo
8e46fade07 fix e2e test 2023-07-20 10:56:54 +09:00
syuilo
871027fa0c enhance: ユーザーにロールが期限付きでアサインされている場合、その期限をユーザーのモデレーションページで確認できるように
Close #11059
2023-07-20 10:54:41 +09:00
syuilo
2ddf575cdc use storybook 7.0.27
Resolve #11318
2023-07-20 10:26:08 +09:00
syuilo
8a058876a3 update aiscript to 0.14.1 2023-07-20 10:24:09 +09:00
syuilo
1df259a7b2 Update about-misskey.vue 2023-07-20 10:21:23 +09:00
syuilo
1e40088d98 enhance(frontend): tweak MkSignupDialog.rules.vue 2023-07-20 10:18:40 +09:00
日高 凌
5e53b41f2d fix(frontend): ユーザー名が長い場合に、フォローボタンを折り返して表示させる (#11326) 2023-07-20 08:11:16 +09:00
woxtu
5f5c4fc44f Remove unused dependencies (#11327) 2023-07-20 06:53:35 +09:00
syuilo
ad9bb773c5 New Crowdin updates (#11312)
* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Thai)
2023-07-19 13:02:28 +09:00
syuilo
59934f9767 Update CHANGELOG.md 2023-07-19 13:01:40 +09:00
xtex
bf9e74ca05 fix: failed to resolve acct URI which points to local Person (#11024)
* fix: resolving alias for local users (#9199)

Signed-off-by: xtex <xtexchooser@duck.com>

* style: return type for RemoteUserResolveService#resolveSelf

Signed-off-by: xtex <xtexchooser@duck.com>

* docs: update CHANGELOG

Signed-off-by: xtex <xtexchooser@duck.com>

* style: fix typecheck

Signed-off-by: xtex <xtexchooser@duck.com>

---------

Signed-off-by: xtex <xtexchooser@duck.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2023-07-19 13:01:14 +09:00
Yangjin Cho
5280a5e5c6 fix(frontend): Fix cat ears are awkward on reply modal (#11309)
* fix(frontend): #11279 adjusted margin & padding

* Revert "fix(frontend): #11279 adjusted margin & padding"

This reverts commit e82a2d482a.

* fix(frontend): adjusted margin & padding
2023-07-19 12:31:38 +09:00
woxtu
8c77153c04 Fix auto-fixable issues (#11314) 2023-07-19 11:27:50 +09:00
syuilo
c50b952ff9 update aiscript to 0.14.0 2023-07-19 11:26:08 +09:00
woxtu
b0b0ecb857 Improve number formatting (#11315) 2023-07-19 07:10:12 +09:00
122 changed files with 4500 additions and 1819 deletions

View File

@@ -82,6 +82,8 @@ redis:
#pass: example-pass
#prefix: example-prefix
#db: 1
# You can specify more ioredis options...
#username: example-username
#redisForPubsub:
# host: localhost
@@ -90,6 +92,8 @@ redis:
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
#redisForJobQueue:
# host: localhost
@@ -98,6 +102,8 @@ redis:
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username
# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────

View File

@@ -8,24 +8,38 @@
-
### Server
-
- Fix: 外部サーバーの投稿がタイムラインに表示されないことがある問題を修正
-->
## 13.x.x (unreleased)
### General
- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました
- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました
-
### Client
- リストTLで、ユーザーが追加・削除されてもTLを初期化しないように
- Fix: モバイル表示のときページ下部がナビゲーションバーに隠れる問題を修正
- Fix: Selecting all emojis in Custom emoji is impossible
### Server
- Fix: APIのオフセットが壊れていたせいで「もっと見る」でもっと見れない問題を修正
## 13.14.1
### General
- 招待機能を改善しました
* 過去に発行した招待コードを確認できるようになりました
* ロールごとに招待コードの発行数制限と制限対象期間、有効期限を設定できるようになりました
* 招待コードを作成したユーザーと使用したユーザーを確認できるようになりました
- ユーザーにロールが期限付きでアサインされている場合、その期限をユーザーのモデレーションページで確認できるようになりました
- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました
- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました
### Client
- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように
- ドライブファイルのメニューで画像をクロップできるように
- 画像を動画と同様に簡単に隠せるように
- Enhance: ノートの埋め込みが複数画像と動画を表示されるように
- オリジナル画像を保持せずにアップロードする場合webpでアップロードされるように(Safari以外)
- 見たことのあるRenoteを省略して表示をオンのときに自分のnoteのrenoteを省略するように
- フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように
@@ -41,6 +55,9 @@
- ロール設定画面でロールIDを確認できるように
- コンテキストメニュー表示時のパフォーマンスを改善
- フォロー/フォロワー非公開時の表示を改善
- 本文にMFMが含まれている場合に自動でたたまれる機能が、返信先や引用RNにも適用されるように
- position は対象外になりました
- AiScriptを0.15.0に更新
- Fix: サーバーメトリクスが90度傾いている
- Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正
- Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正
@@ -57,14 +74,19 @@
- nsfwjs のモデルロードを排他することで、重複ロードによってメモリ使用量が増加しないように
- 連合の配送ジョブのパフォーマンスを向上ロック機構の見直し、Redisキャッシュの活用
- featuredートのsignedGet回数を減らしました
- リモートサーバーからのNSFW映像のキャッシュだけを無効化できるオプションを追加
- ActivityPubの署名用鍵長を2048bitに変更しパフォーマンスを向上(新規アカウントのみ)
- リモートサーバーのセンシティブなファイルのキャッシュだけを無効化できるオプションを追加
- MeilisearchにIndexするートの範囲を設定できるように
- Export notes with file detail
- Add unix socket support
- 設定ファイルでioredisの全てのオプションを指定可能に
- Fix: エクスポートしたカスタム絵文字のzipが大きいと読み込めない問題を修正
- Fix: リモートサーバーに無意味なActivityPubの配信を行うことがあるのを修正
- Fix: Remove Meilisearch index when notes are deleted
- Fix: 非英語環境でのPostgreSQLのエラーハンドリングを修正
- Fix: インスタンスのアイコンがbase64の場合の挙動を修正
- Fix: ローカルの `Person` を指す `acct` URI を解析するときのバグを修正しました
- Fix: 無効化されたアンテナが再度有効化されないことがある問題を修正
## 13.13.2

View File

@@ -214,30 +214,13 @@ Misskey uses [Storybook](https://storybook.js.org/) for UI development.
### Setup & Run
#### Universal
##### Setup
```bash
pnpm --filter misskey-js build
pnpm --filter frontend tsc -p .storybook && (node packages/frontend/.storybook/preload-locale.js & node packages/frontend/.storybook/preload-theme.js)
```
##### Run
```bash
node packages/frontend/.storybook/generate.js && pnpm --filter frontend storybook dev
```
#### macOS & Linux
##### Setup
#### Setup
```bash
pnpm --filter misskey-js build
```
##### Run
#### Run
```bash
pnpm --filter frontend storybook-dev

View File

@@ -54,6 +54,7 @@ describe('After setup instance', () => {
cy.get('[data-cy-signup]').click();
cy.get('[data-cy-signup-rules-continue]').should('be.disabled');
cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click();
cy.get('[data-cy-modal-dialog-ok]').click();
cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled');
cy.get('[data-cy-signup-rules-continue]').click();
@@ -78,6 +79,7 @@ describe('After setup instance', () => {
cy.get('[data-cy-signup]').click();
cy.get('[data-cy-signup-rules-continue]').should('be.disabled');
cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click();
cy.get('[data-cy-modal-dialog-ok]').click();
cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled');
cy.get('[data-cy-signup-rules-continue]').click();

View File

@@ -2,6 +2,7 @@
_lang_: "Čeština"
headlineMisskey: "Síť propojená poznámkami"
introMisskey: "Vítejte! Misskey je otevřený a decentralizovaný microblogový servis.\n\"Poznámkami\" můžete sdílet co se zrovna děje se všemi ve Vašem okolí. 📡\nPomocí \"reakcí\" můžete sdílet své názory a pocity na ostatní poznámky. 👍\nPojďte objevovat nový svět! 🚀"
poweredByMisskeyDescription: "{name} je jeden ze serverů využívající open source platformu <b>Misskey<b> (nazývaná \"Misskey instance\")."
monthAndDay: "{day}. {month}."
search: "Vyhledávání"
notifications: "Oznámení"
@@ -48,8 +49,15 @@ delete: "Smazat"
deleteAndEdit: "Smazat a upravit"
deleteAndEditConfirm: "Jste si jistí že chcete smazat tuto poznámku a editovat ji? Ztratíte tím všechny reakce, sdílení a odpovědi na ni."
addToList: "Přidat do seznamu"
addToAntenna: "Přidat do antény"
sendMessage: "Odeslat zprávu"
copyRSS: "Kopírovat RSS"
copyUsername: "Kopírovat uživatelské jméno"
copyUserId: "Kopírovat ID uživatele"
copyNoteId: "Kopírovat ID poznámky"
copyFileId: "Kopírovat ID souboru"
copyFolderId: "Kopírovat ID složky"
copyProfileUrl: "Kopírovat URL profilu"
searchUser: "Vyhledat uživatele"
reply: "Odpovědět"
loadMore: "Zobrazit více"
@@ -60,6 +68,7 @@ receiveFollowRequest: "Žádost o sledování přijata"
followRequestAccepted: "Žádost o sledování přijata"
mention: "Zmínění"
mentions: "Zmínění"
directNotes: "Přímé poznámky"
importAndExport: "Import a export"
import: "Importovat"
export: "Exportovat"
@@ -82,6 +91,7 @@ error: "Chyba"
somethingHappened: "Jejda. Něco se nepovedlo."
retry: "Opakovat"
pageLoadError: "Nepodařilo se načíst stránku"
pageLoadErrorDescription: "Tohle je obvykle způsobeno chybou sítě nebo mezipaměti prohlížeče. Zkuste vymazat mezipaměť a po chvíli čekání to zkuste znovu."
serverIsDead: "Server neodpovídá. Počkejte chvíli a zkuste to znovu."
youShouldUpgradeClient: "Pro zobrazení této stránky obnovte stránku pro aktualizaci klienta."
enterListName: "Jméno seznamu"
@@ -100,6 +110,8 @@ renoted: "Přeposláno"
cantRenote: "Tento příspěvek nelze přeposlat."
cantReRenote: "Odpověď nemůže být odstraněna."
quote: "Citovat"
inChannelRenote: "Přeposlání v kanálu"
inChannelQuote: "Citace v kanálu"
pinnedNote: "Připnutá poznámka"
pinned: "Připnout"
you: "Vy"
@@ -108,6 +120,7 @@ sensitive: "NSFW"
add: "Přidat"
reaction: "Reakce"
reactions: "Reakce"
reactionSetting: "Reakce zobrazené ve výběru reakcí"
reactionSettingDescription2: "Přetažením změníte pořadí, kliknutím smažete, zmáčkněte \"+\" k přidání"
rememberNoteVisibility: "Zapamatovat nastavení zobrazení poznámky"
attachCancel: "Odstranit přílohu"
@@ -116,6 +129,8 @@ unmarkAsSensitive: "Odznačit jako NSFW"
enterFileName: "Zadejte název souboru"
mute: "Ztlumit"
unmute: "Odmlčet"
renoteMute: "Ztlumit poznámky"
renoteUnmute: "Zrušit ztlumení poznámek"
block: "Zablokovat"
unblock: "Odblokovat"
suspend: "Zmrazit"
@@ -125,7 +140,10 @@ unblockConfirm: "Jste si jistí že chcete odblokovat tento účet?"
suspendConfirm: "Jste si jistí že chcete suspendovat tenhle účet?"
unsuspendConfirm: "Jste si jistí že chcete obnovit tenhle účet?"
selectList: "Vybrat seznam"
editList: "Upravit seznam"
selectChannel: "Vybrat kanál"
selectAntenna: "Vyberte Anténu"
editAntenna: "Upravit anténu"
selectWidget: "Zvolte widget"
editWidgets: "Upravit widget"
editWidgetsExit: "Hotovo"
@@ -138,6 +156,8 @@ addEmoji: "Přidat emoji"
settingGuide: "Doporučené nastavení"
cacheRemoteFiles: "Ukládání vzdálených souborů do mezipaměti"
cacheRemoteFilesDescription: "Zakázání tohoto nastavení způsobí, že vzdálené soubory budou odkazovány přímo, místo aby byly ukládány do mezipaměti. Tím se ušetří úložiště na serveru, ale zvýší se provoz, protože se negenerují miniatury."
cacheRemoteSensitiveFiles: "Uložit do mezipaměti vzdálené citlivé soubory"
cacheRemoteSensitiveFilesDescription: "Když je tohle nastavení zrušeno, tak jsou vzdálené citlivé soubory načítány přímo ze vzdálených instancí bez uložení do mezipaměti."
flagAsBot: "Tento účet je bot"
flagAsBotDescription: "Pokud je tento účet kontrolován programem zaškrtněte tuto možnost. To označí tento účet jako bot pro ostatní vývojáře a zabrání tak nekonečným interakcím s ostatními boty a upraví Misskey systém aby se choval k tomuhle účtu jako bot."
flagAsCat: "Tenhle účet je kočka"
@@ -146,6 +166,7 @@ flagShowTimelineReplies: "Zobrazovat odpovědi na časové ose"
flagShowTimelineRepliesDescription: "Je-li zapnuto, zobrazí odpovědi uživatelů na poznámky jiných uživatelů na vaší časové ose."
autoAcceptFollowed: "Automaticky akceptovat následování od účtů které sledujete"
addAccount: "Přidat účet"
reloadAccountsList: "Obnovit list účtů"
loginFailed: "Přihlášení se nezdařilo."
showOnRemote: "Více na původním profilu"
general: "Obecně"
@@ -186,17 +207,26 @@ instanceInfo: "Informace o instanci"
statistics: "Statistiky"
clearQueue: "Vyčistit frontu"
clearQueueConfirmTitle: "Jste si jisti že zrušit všechny úlohy ve frontě?"
clearQueueConfirmText: "Jakékoliv nedoručené poznámky ve frontě nebudou sdružovány. Většinou tahle operace není zapotřebí."
clearCachedFiles: "Vyprázdnit mezipaměť"
clearCachedFilesConfirm: "Jste jistí že chcete smazat všechny vzdálené soubory v mezipaměti?"
blockedInstances: "Blokované instance"
blockedInstancesDescription: "Vypište názvy hostitelů instancí, které chcete blokovat odděleně řádkovými zlomky. Uvedené instance již nebudou moci s touto instancí komunikovat."
muteAndBlock: "Ztlumení a blokování"
mutedUsers: "Zltumení uživatelé"
blockedUsers: "Blokovaní uživatelé"
noUsers: "Žádní uživatelé"
editProfile: "Upravit můj profil"
noteDeleteConfirm: "Jste si jistí že chcete smazat tuhle poznámku?"
pinLimitExceeded: "Nemůžete připnout další poznámky."
intro: "Instalace Misskey byla dokončena! Prosím vytvořte admina."
done: "Hotovo"
processing: "Zpracovávám"
preview: "Náhled"
default: "Výchozí"
defaultValueIs: "Základní hodnota: {value}"
noCustomEmojis: "Bez Emoji"
federating: "Sdružování"
blocked: "Blokováno"
suspended: "Suspendováno"
all: "Vše"
@@ -217,6 +247,7 @@ more: "Více!"
featured: "Oblíbené poznámky"
usernameOrUserId: "Uživatelské jméno nebo uživatelské id"
noSuchUser: "Uživatel nebyl nalezen"
lookup: "Vyhledat"
announcements: "Oznámení"
imageUrl: "URL obrázku"
remove: "Smazat"
@@ -227,10 +258,13 @@ resetAreYouSure: "Opravdu resetovat?"
saved: "Uloženo"
messaging: "Zprávy"
upload: "Nahrát soubory"
keepOriginalUploading: "Ponechat originální obrázek"
keepOriginalUploadingDescription: "Uloží původní nahraný obrázek jak je. Pokud je to vypnuté, vygeneruje se zobrazení verze na webu při nahrátí."
fromDrive: "Z disku"
fromUrl: "Z URL"
uploadFromUrl: "Nahrát z URL adresy"
uploadFromUrlDescription: "URL adresa souboru, který chcete nahrát"
uploadFromUrlRequested: "Upload zažádán"
uploadFromUrlMayTakeTime: "Může trvat nějakou dobu, dokud nebude dokončeno nahrávání."
explore: "Objevovat"
messageRead: "Přečtené"
@@ -238,6 +272,10 @@ noMoreHistory: "To je vše"
startMessaging: "Zahájit chat"
nUsersRead: "přečteno {n} uživateli"
agreeTo: "Souhlasím s {0}"
agree: "Souhlasím"
agreeBelow: "Souhlasím s následným"
basicNotesBeforeCreateAccount: "Důležité poznámky"
termsOfService: "Podmínky užívání"
start: "Začít"
home: "Domů"
remoteUserCaution: "Tyto informace nemusí být aktuální jelikož uživatel je ze vzdálené instance."
@@ -268,17 +306,24 @@ createFolder: "Vytvořit složku"
renameFolder: "Přejmenovat složku"
deleteFolder: "Odstranit složku"
addFile: "Přidat soubor"
emptyDrive: "Váš disk je prázdný"
emptyFolder: "Tato složka je prázdná"
unableToDelete: "Nelze smazat"
inputNewFileName: "Zadejte nový název"
inputNewDescription: "Zadejte nový popisek"
inputNewFolderName: "Zadejte název nové složky"
circularReferenceFolder: "Koncová složka je podsložka složky, kterou chcete přesunout."
hasChildFilesOrFolders: "Nemůžete odstranit složku, která není prázdná."
copyUrl: "Kopírovat URL"
rename: "Přejmenovat"
avatar: "Avatar"
banner: "Baner"
displayOfSensitiveMedia: "Zobrazit citlivé média"
whenServerDisconnected: "Když ztratíte spojení se serverem"
disconnectedFromServer: "Spojení bylo přerušeno"
reload: "Aktualizovat"
doNothing: "Ignorovat"
reloadConfirm: "Chcete obnovit časovou osu?"
watch: "Sledovat"
unwatch: "Přestat sledovat"
accept: "Souhlasím"
@@ -301,15 +346,22 @@ connectService: "Připojit"
disconnectService: "Odpojit"
enableLocalTimeline: "Povolit lokální čas"
enableGlobalTimeline: "Povolit globální čas"
disablingTimelinesInfo: "Administrátoři a Moderátoři budou mít stálý přístup ke všem časovým osám i přes to že nejsou zapnuté."
registration: "Registrace"
enableRegistration: "Povolit registraci novým uživatelům"
invite: "Pozvat"
driveCapacityPerLocalAccount: "Kapacita disku na lokálního uživatele"
driveCapacityPerRemoteAccount: "Kapacita disku na vzdáleného uživatele"
inMb: "V megabajtech"
iconUrl: "Favicon URL"
bannerUrl: "Baner URL"
backgroundImageUrl: "Adresa URL obrázku pozadí"
basicInfo: "Základní informace"
pinnedUsers: "Připnutí uživatelé"
pinnedUsersDescription: "Seznam uživatelských přezdívek oddělených řádkami bude připnutý v záložce \"Objevit\"."
pinnedPages: "Připnutý stránky"
pinnedPagesDescription: "Zadejte cesty stránek oddělené řádkami, které si přejete mít přípnutý na vrcholu téhle instance."
pinnedClipId: "ID připnutého klipu"
pinnedNotes: "Připnutá poznámka"
hcaptcha: "hCaptcha"
enableHcaptcha: "Aktivovat hCaptchu"
@@ -319,30 +371,56 @@ recaptcha: "reCAPTCHA"
enableRecaptcha: "Zapnout ReCAPTCHu"
recaptchaSiteKey: "Klíč stránky"
recaptchaSecretKey: "Tajný Klíč (Secret Key)"
turnstile: "Turnstile"
enableTurnstile: "Povolit Turnstile"
turnstileSiteKey: "Klíč stránky"
turnstileSecretKey: "Tajný Klíč (Secret Key)"
avoidMultiCaptchaConfirm: "Používání několik Captcha systému může způsobit konflikt mezi nimi. Chtěli byste vypnout ostatní aktivní Captcha systémy? Pokud je chcete nechat zapnuté, stiskněte zrušit."
antennas: "Antény"
manageAntennas: "Spravovat Antény"
name: "Jméno"
antennaSource: "Zdroj Antény"
antennaKeywords: "Klíčová slova na poslech"
antennaExcludeKeywords: "Vyloučená klíčová slova"
antennaKeywordsDescription: "Oddělte mezerami pro AND kondice nebo řádkami pro OR kondice."
notifyAntenna: "Upozornit na nové poznámky"
withFileAntenna: "Poznámky jenom se souborama"
enableServiceworker: "Povolit ServiceWorker"
antennaUsersDescription: "Vypsat jednoho uživatele na řádek"
caseSensitive: "Rozlišuje malá a velká písmena"
withReplies: "Zahrnout odpovědi"
connectedTo: "Následující účty jsou připojeny"
notesAndReplies: "Poznámky a odpovědi"
withFiles: "Včetně souborů"
silence: "Ztlumení"
silenceConfirm: "Jste si jistí že chcete ztlumit tohoto uživatele?"
unsilence: "Zrušit ztlumení"
unsilenceConfirm: "Jste jistí že chcete vrátit zltumení tohoto uživatele?"
popularUsers: "Populární uživatelé"
recentlyUpdatedUsers: "Nedávno aktívni uživatelé"
recentlyRegisteredUsers: "Nově připojený uživatelé"
recentlyDiscoveredUsers: "Nově objevený uživatelé"
exploreUsersCount: "Existuje {count} uživatelů"
exploreFediverse: "Objevovat Fediverse"
popularTags: "Populární tagy"
userList: "Seznamy"
about: "Informace"
aboutMisskey: "O Misskey"
administrator: "Administrátor"
token: "Token"
2fa: "Dvoufázové ověření"
totp: "Ověřovací aplikace"
totpDescription: "Použít ověřovací aplikaci pro použití jednorázových hesel"
moderator: "Moderátor"
moderation: "Moderování"
nUsersMentioned: "{n} uživatelů zmínilo"
securityKeyAndPasskey: "Bezpečnostní klíče a tokeny"
securityKey: "Bezpečnostní klíč"
lastUsed: "Naposledy použito"
lastUsedAt: "Naposledy použito: {t}"
unregister: "Odstranit"
passwordLessLogin: "Přihlášení bez hesla"
passwordLessLoginDescription: "Umožní bez-heslové přihlášení pomocí bezpečnostního klíče či tokenu"
resetPassword: "Resetovat heslo"
newPasswordIs: "Nové heslo je \"{password}\""
reduceUiAnimation: "Snížit UI animace"
@@ -391,14 +469,25 @@ or: "Nebo"
language: "Jazyk"
uiLanguage: "Jazyk uživatelského rozhraní"
aboutX: "O {x}"
emojiStyle: "Styl emoji"
native: "Výchozí"
disableDrawer: "Nepoužívat šuplíkové menu"
showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
noHistory: "Žádná historie"
signinHistory: "Historie přihlášení"
enableAdvancedMfm: "Zapnout pokročilé MFM"
enableAnimatedMfm: "Zapnout animované MFM"
doing: "Procesuju..."
category: "Kategorie"
tags: "Štítky"
docSource: "Zdroj tohoto dokumentu"
createAccount: "Vytvořit účet"
existingAccount: "Existující účet"
regenerate: "Obnovit"
fontSize: "Velikost písma"
mediaListWithOneImageAppearance: "Výška seznamu médií s jedním obrázkem"
limitTo: "Omezeno na {x}"
noFollowRequests: "Nemáte žádné žádosti o sledování"
openImageInNewTab: "Otevřít obrázek v novém panelu"
dashboard: "Přehled"
local: "Lokální"
@@ -412,15 +501,35 @@ accountSettings: "Nastavení účtu"
promotion: "Propagace"
promote: "Propagovat"
numberOfDays: "Počet dní"
hideThisNote: "Skrýt tuto poznámku"
showFeaturedNotesInTimeline: "Zobrazit významné poznámky v časové ose"
objectStorage: "Úložiště objektů"
useObjectStorage: "Použít úložiště objektů"
objectStorageBaseUrl: "Base URL"
objectStorageBaseUrlDesc: "URL použitá jako reference. Upřesněte URL vlastní CDN nebo Proxy pokud používáte jeden z nich. Pro S3 použijte 'https://<bucket>.s3.amazonaws.com' a pro GCS nebo ekvivalentní služby použijte 'https://storage.googleapis.com/<bucket>', apd."
objectStorageBucket: "Bucket"
objectStorageBucketDesc: "Prosím upřesněte název bucketu používaný poskytovatelem."
objectStoragePrefix: "Předpona"
objectStoragePrefixDesc: "Soubory budou ukládány pod složkama s tímhle prefixem."
objectStorageEndpoint: "Endpoint"
objectStorageEndpointDesc: "Ponechte tohle prázdné pokud používáte AWS S3, jinak upřesněte endpoint jako \"<host>\" nebo \"<host>:<port>\", podle toho jakou službu používáte."
objectStorageRegion: "Región"
objectStorageRegionDesc: "Upřesněte region jako například \"xx-east-1\". Pokud vlastní služba nerozlišuje mezi regiony, zadejte \"us-east-1\". Zanechte prázdné pokud používáte AWS konfiguraci či proměnné veličiny."
objectStorageUseSSL: "Použít SSL"
objectStorageUseSSLDesc: "Vypněte to pokud nebudete používat HTTPS pro API připojení"
objectStorageUseProxy: "Připojení skrze Proxy"
objectStorageUseProxyDesc: "Vypněte to pokud nebudete používat Proxy pro API připojení."
objectStorageSetPublicRead: "Při nahrátí nastavit na \"public-read\""
s3ForcePathStyleDesc: "Pokud je povolena funkce s3ForcePathStyle, musí být název Bucketu zahrnut do cesty k adrese URL, nikoli do názvu hostitele adresy URL. Toto nastavení může být nutné povolit při používání služeb, jako je například samostatně hostovaná instance Minio."
serverLogs: "Logy serveru"
deleteAll: "Smazat vše"
showFixedPostForm: "Zobrazit formulář pro nové příspěvky nad časovou osou"
showFixedPostFormInChannel: "Zobrazit vkládací formulář na vrcholu časové osy (Kanály)"
newNoteRecived: "Jsou k dispozici nové poznámky"
sounds: "Zvuky"
sound: "Zvuky"
listen: "Poslouchat"
none: "Žádný"
showInPage: "Zobrazit na stránce"
popout: "Pop-out"
volume: "Hlasitost"
@@ -433,29 +542,61 @@ install: "Nainstalovat"
uninstall: "Odinstalovat"
installedApps: "Autorizované aplikace"
nothing: "Nic nebylo nalezeno"
installedDate: "Datum autorizace"
lastUsedDate: "Poslední použití"
state: "Stav"
sort: "Seřadit"
ascendingOrder: "Vzestupně"
descendingOrder: "Sestupně"
scratchpad: "Zápisník"
scratchpadDescription: "Scratchpad poskytuje rozhraní pro AiScript experimenty. Můžete psát, spustit či zkontrolovat výsledky jeho interakce s Misskey."
output: "Výstup"
script: "Skript"
disablePagesScript: "Vypnout AiScript na stránkách"
updateRemoteUser: "Aktualizovat informace o vzdáleném účtu"
deleteAllFiles: "Smazat všechny soubory"
deleteAllFilesConfirm: "Jste si jistí že chcete smazat všechny soubory?"
removeAllFollowing: "Přestat sledovat všechny sledované uživatele"
removeAllFollowingDescription: "Spuštěním přestanete sledovat všechny účty z {host}. Prosíme spustěte tohle v případě že instance už neexistuje. "
userSuspended: "Tomuto uživateli byl pozastaven účet."
userSilenced: "Tenhle uživatel je umlčen."
yourAccountSuspendedTitle: "Tenhle účet je zmrazený"
yourAccountSuspendedDescription: "Tenhle účet byl zmrazen z důvodu porušení smluvní podmínky serveru. Pro přesnější informace kontaktujte administrátora. Prosíme nezakládejte si nový účet."
tokenRevoked: "Nesprávný token"
tokenRevokedDescription: "Tenhle token vyprchal. Prosíme přihlašte se znova."
accountDeleted: "Účet smazán"
accountDeletedDescription: "Tenhle účet byl smazán."
menu: "Menu"
divider: "Dělící čára"
addItem: "Přidat položku"
rearrange: "Přeřadit"
relays: "Relay"
addRelay: "Přidat Relay"
inboxUrl: "Inbox URL"
addedRelays: "Přidané přenosy"
serviceworkerInfo: "Musí být zapnut pro push notifikace."
deletedNote: "Odstraněné příspěvky"
invisibleNote: "Skryté příspěvky"
enableInfiniteScroll: "Automaticky načítat více"
visibility: "Viditelnost"
poll: "Anketa"
useCw: "Schovat obsah"
enablePlayer: "Otevřít video přehrávač"
disablePlayer: "Zavřít video přehrávač"
expandTweet: "Rozbalit tweet"
themeEditor: "Editor témat"
description: "Popis"
describeFile: "Přidat popisek"
enterFileDescription: "Vložit popisek"
author: "Autor"
leaveConfirm: "Máte neuložené změny. Opravdu je chcete zahodit?"
manage: "Administrace"
plugins: "Pluginy"
preferencesBackups: "Zálohy nastavení"
deck: "Deck"
undeck: "Opustit Deck"
useBlurEffectForModal: "Použít efekt rozostření na okna"
useFullReactionPicker: "Používat plnou velikost výběru emoji"
width: "Šířka"
height: "Výška"
large: "Velké"
@@ -465,10 +606,13 @@ generateAccessToken: "Vygenerovat přístupový token"
permission: "Oprávnění"
enableAll: "Povolit vše"
disableAll: "Vypnout vše"
tokenRequested: "Povolit přístup k účtu"
pluginTokenRequestedDescription: "Tenhle plugin bude moct používat oprávnění nastavená zde."
notificationType: "Typy oznámení"
edit: "Upravit"
emailServer: "Mailový server"
enableEmail: "Zapnout email dystribuci"
emailConfigInfo: "Používá se na ověření emailové adresy během registrace nebo při zapomenutí hesla."
email: "Email"
emailAddress: "Emailová adresa"
smtpConfig: "Konfigurace SMTP serveru"
@@ -476,8 +620,15 @@ smtpHost: "Hostitel"
smtpPort: "Port"
smtpUser: "Uživatelské jméno"
smtpPass: "Heslo"
emptyToDisableSmtpAuth: "Zanechte uživatelské jméno a heslo prázdné pro vypnutí SMTP verifikace."
smtpSecure: "Použít implicitní SSL/TLS pro SMTP připojení"
smtpSecureInfo: "Toto vypněte pokud používáte STARTTLS"
testEmail: "Otestovat doručení emailů"
wordMute: "Ztlumené slova"
regexpError: "Chyba v regulérním výrazu"
regexpErrorDescription: "Došlo k chybě v regulérním výrazu v řádku {line} tabulky {tab} ztlumených slov:"
instanceMute: "Ztlumené instance"
userSaysSomething: "{name} řekl/a něco"
makeActive: "Aktivovat"
display: "Zobrazit"
copy: "Kopírovat"
@@ -489,21 +640,66 @@ database: "Databáze"
channel: "Kanály"
create: "Vytvořit"
notificationSetting: "Nastavení oznámení"
notificationSettingDesc: "Vyberte typy oznámení k zobrazení."
useGlobalSetting: "Použít globální nastavení"
useGlobalSettingDesc: "Pokud je to zapnuté, tak nastavení oznámení účtu bude použito. Pokud je to vypnuté, tak se bude moct použít jednotlivá nastavení."
other: "Ostatní"
regenerateLoginToken: "Přegenerovat přihlašovací token"
regenerateLoginTokenDescription: "Přegeneruje token interně používaný během přihlášení. Běžně tahle akce není nutná. Pokud bude token přegenerovaný, tak se všechna přihlášená zařízení odhlásí."
setMultipleBySeparatingWithSpace: "Oddělení více položek mezerami."
fileIdOrUrl: "ID nebo URL souboru"
behavior: "Chování"
sample: "Ukázka"
abuseReports: "Nahlášení"
reportAbuse: "Nahlášení"
reportAbuseOf: "Nahlásit {name}"
fillAbuseReportDescription: "Prosíme vyplňte všechny detaily ohledně tohodle nahlášení. Pokud jde o specifickou poznámku, prosíme o přiložení její URL."
abuseReported: "Nahlášení bylo odesláno. Děkujeme převelice."
reporter: "Nahlásil"
reporteeOrigin: "Původ nahlášení"
reporterOrigin: "Původ nahlasovače"
forwardReport: "Přeposlat nahlášení do vzdálené instance"
send: "Odeslat"
openInNewTab: "Otevřít v nové kartě"
openInSideView: "Otevřít v bočním panelu"
defaultNavigationBehaviour: "Výchozí chování navigace"
instanceTicker: "Informace instance o poznámkách"
waitingFor: "Čeká se na {x}"
random: "Náhodně"
system: "Systém"
switchUi: "Přepnout UI"
desktop: "Plocha"
clip: "Oříznout"
createNew: "Vytvořit nový"
optional: "Volitelné"
createNewClip: "Vytvořit nový klip"
unclip: "Odepnout"
confirmToUnclipAlreadyClippedNote: "Tahle poznámku je už součásti \"{name}\" klipu. Chcete ji místo toho odepnout z tohodle klipu?"
public: "Veřejný"
i18nInfo: "Misskey je překládán do jiných jazyků dobrovolníkama. Můžete pomoci na {link}."
manageAccessTokens: "Spravovat přístupové tokeny"
accountInfo: "Informace o účtu"
notesCount: "Počet poznámek"
repliesCount: "Počet odeslaných odpovědí"
renotesCount: "Počet přeposlaných poznámek"
repliedCount: "Počet přijatých odpovědí"
renotedCount: "Počet přijatých přeposlaných poznámek"
followingCount: "Počet sledovaných účtů"
followersCount: "Počet sledujících"
sentReactionsCount: "Počet odeslaných reakcí"
receivedReactionsCount: "Počet přijatých reakcí"
pollVotesCount: "Počet odeslaných anketových hlasů"
pollVotedCount: "Počet přijatých anketových hlasů"
yes: "Ano"
no: "Ne"
driveFilesCount: "Počet souborů na disku"
driveUsage: "Využití disku"
noCrawle: "Odmítat indexování crawleru"
lockedAccountInfo: "Pokud nenastavíte viditelnost poznámek na \"Pouze pro sledující\", budou poznámky viditelné všem i přesto že vyžadujete manuální potvrzení pro sledování."
alwaysMarkSensitive: "Výchozně označovat jako citlivý"
loadRawImages: "Načítat originální obrázky místo náhledů"
disableShowingAnimatedImages: "Nepřehrávat animované obrázky"
verificationEmailSent: "Ověřovací email byl zaslán. Ověření dokončíte kliknutím na odkaz v emailu."
notSet: "Není nastaveno"
emailVerified: "Váš e-mail byl ověřen"
contact: "Kontakt"

View File

@@ -1091,6 +1091,9 @@ usedAt: "Benutzt am"
unused: "Unbenutzt"
used: "Benutzt"
expired: "Abgelaufen"
doYouAgree: "Zustimmen?"
beSureToReadThisAsItIsImportant: "Lies bitte diese wichtige Informationen."
iHaveReadXCarefullyAndAgree: "Ich habe den Text \"{x}\" gelesen und stimme zu."
_initialAccountSetting:
accountCreated: "Dein Konto wurde erfolgreich erstellt!"
letsStartAccountSetup: "Lass uns nun dein Konto einrichten."

View File

@@ -1091,6 +1091,9 @@ usedAt: "Used at"
unused: "Unused"
used: "Used"
expired: "Expired"
doYouAgree: "Agree?"
beSureToReadThisAsItIsImportant: "Please read this important information."
iHaveReadXCarefullyAndAgree: "I have read the text \"{x}\" and agree."
_initialAccountSetting:
accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile."

View File

@@ -1091,6 +1091,9 @@ usedAt: "Usada el"
unused: "Sin usar"
used: "Usada"
expired: "Caducada"
doYouAgree: "¿Está de acuerdo?"
beSureToReadThisAsItIsImportant: "Por favor lea esto que es importante"
iHaveReadXCarefullyAndAgree: "He leído el texto {x} y estoy de acuerdo"
_initialAccountSetting:
accountCreated: "¡La cuenta ha sido creada!"
letsStartAccountSetup: "Para empezar, creemos tu perfil."

View File

@@ -49,6 +49,7 @@ delete: "Supprimer"
deleteAndEdit: "Supprimer et réécrire"
deleteAndEditConfirm: "Êtes-vous sûr·e de vouloir supprimer cette note et la reformuler ? Vous perdrez toutes les réactions, renotes et réponses y afférentes."
addToList: "Ajouter à une liste"
addToAntenna: "Ajouter à lantenne"
sendMessage: "Envoyer un message"
copyRSS: "Copier le RSS"
copyUsername: "Copier le nom dutilisateur·rice"

1
locales/hu-HU.yml Normal file
View File

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

3
locales/index.d.ts vendored
View File

@@ -1094,6 +1094,9 @@ export interface Locale {
"unused": string;
"used": string;
"expired": string;
"doYouAgree": string;
"beSureToReadThisAsItIsImportant": string;
"iHaveReadXCarefullyAndAgree": string;
"_initialAccountSetting": {
"accountCreated": string;
"letsStartAccountSetup": string;

View File

@@ -1042,7 +1042,7 @@ vertical: "縦"
horizontal: "横"
position: "位置"
serverRules: "サーバールール"
pleaseConfirmBelowBeforeSignup: "このサーバーに登録するに、以下を確認してください。"
pleaseConfirmBelowBeforeSignup: "このサーバーに登録するに、以下の内容を確認し同意する必要があります。"
pleaseAgreeAllToContinue: "続けるには、全ての「同意する」にチェックが入っている必要があります。"
continue: "続ける"
preservedUsernames: "予約ユーザー名"
@@ -1091,6 +1091,9 @@ usedAt: "使用日時"
unused: "未使用"
used: "使用済み"
expired: "期限切れ"
doYouAgree: "同意しますか?"
beSureToReadThisAsItIsImportant: "重要ですので必ずお読みください。"
iHaveReadXCarefullyAndAgree: "「{x}」の内容をよく読み、同意します。"
_initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!"

View File

@@ -49,11 +49,15 @@ delete: "ほかす"
deleteAndEdit: "ほかして直す"
deleteAndEditConfirm: "このートをほかしてもっかい直すこのートへのツッコミ、Renote、返信も全部消えるんやけどそれでもええん"
addToList: "リストに入れたる"
addToAntenna: "アンテナに追加"
sendMessage: "メッセージを送る"
copyRSS: "RSSをコピー"
copyUsername: "ユーザー名をコピー"
copyUserId: "ユーザーIDをコピー"
copyNoteId: "ートIDをコピー"
copyFileId: "ファイルIDをコピー"
copyFolderId: "フォルダーIDをコピー"
copyProfileUrl: "プロフィールURLをコピー"
searchUser: "ユーザーを検索"
reply: "返事"
loadMore: "まだまだあるで!"
@@ -152,6 +156,8 @@ addEmoji: "絵文字を追加"
settingGuide: "ええ感じの設定"
cacheRemoteFiles: "リモートのファイルをキャッシュする"
cacheRemoteFilesDescription: "この設定を切っとったら、リモートファイルをキャッシュせんと直リンクするようになるで。サーバーの容量は節約できるけど、サムネイルを作らんなるから通信量が増えるで。"
cacheRemoteSensitiveFiles: "リモートのセンシティブなファイルをキャッシュする"
cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになるで。"
flagAsBot: "Botにするで"
flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Misskeyのシステム上での扱いがBotに合ったもんになるからな。"
flagAsCat: "Catやで"
@@ -313,6 +319,7 @@ copyUrl: "URLをコピー"
rename: "名前を変えるで"
avatar: "アイコン"
banner: "バナー"
displayOfSensitiveMedia: "センシティブなメディアの表示"
whenServerDisconnected: "サーバーとの接続が失くなってしもうたとき"
disconnectedFromServer: "サーバーが機嫌悪いねん"
reload: "リロード"
@@ -1067,8 +1074,26 @@ branding: "あ"
enableServerMachineStats: "サーバーのマシン情報見せびらかすで"
enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
turnOffToImprovePerformance: "オフにしたらえらい軽うなるで。"
createInviteCode: "招待コードを作成"
createWithOptions: "オプションを指定して作成"
createCount: "作成数"
inviteCodeCreated: "招待コード作ったで"
inviteLimitExceeded: "招待コード作りすぎやで。"
createLimitRemaining: "作成できる招待コード: 残り {limit} 個やで"
inviteLimitResetCycle: "{time}で最大 {limit} 個の招待コードを作成できるで。"
expirationDate: "有効期限"
noExpirationDate: "有効期限を設けへん"
inviteCodeUsedAt: "招待コードが使用された日時"
registeredUserUsingInviteCode: "招待コードを使用したユーザー"
waitingForMailAuth: "メール認証待ち"
inviteCodeCreator: "招待コードを作成したユーザー"
usedAt: "使用日時"
unused: "つこてへん"
used: "もうつこてる"
expired: "期限切れ"
doYouAgree: "同意するんか?"
beSureToReadThisAsItIsImportant: "重要やから絶対読んでや。"
iHaveReadXCarefullyAndAgree: "「{x}」の内容をよう読んで、同意するで。"
_initialAccountSetting:
accountCreated: "アカウント作り終わったで。"
letsStartAccountSetup: "アカウントの初期設定をしよか。"
@@ -1379,6 +1404,9 @@ _role:
ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可"
canInvite: "サーバー招待コードの発行"
inviteLimit: "招待コードの作成可能数"
inviteLimitCycle: "招待コードの発行間隔"
inviteExpirationTime: "招待コードの有効期限"
canManageCustomEmojis: "カスタム絵文字の管理"
driveCapacity: "ドライブ容量"
alwaysMarkNsfw: "勝手にファイルにNSFWをくっつける"
@@ -1441,6 +1469,7 @@ _ad:
back: "戻る"
reduceFrequencyOfThisAd: "この広告の表示頻度を下げるで"
hide: "表示せん"
timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されるで。"
_forgotPassword:
enterEmail: "アカウントに登録したメールアドレスをここに入力してや。そのアドレス宛に、パスワードリセット用のリンクが送られるから待っててな~。"
ifNoEmail: "メールアドレスを登録してへんのやったら、管理者まで教えてな~。"
@@ -1970,6 +1999,7 @@ _deck:
introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょ!"
introduction2: "画面の右にある + を押して、いつでもカラムを追加できるで。"
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選んでウィジェットを追加してなー"
useSimpleUiForNonRootPages: "非ルートページは簡易UIで表示"
_columns:
main: "メイン"
widgets: "ウィジェット"

View File

@@ -40,7 +40,7 @@ favorites: "즐겨찾기"
unfavorite: "즐겨찾기에서 제거"
favorited: "즐겨찾기에 등록했습니다"
alreadyFavorited: "이미 즐겨찾기에 등록되어 있습니다"
cantFavorite: "즐겨찾기에 등록하지 못했습니다."
cantFavorite: "즐겨찾기에 등록하지 못했습니다"
pin: "프로필에 고정"
unpin: "프로필에서 고정 해제"
copyContent: "내용 복사"
@@ -55,8 +55,8 @@ copyRSS: "RSS 복사"
copyUsername: "유저명 복사"
copyUserId: "유저 ID 복사"
copyNoteId: "노트 ID 복사"
copyFileId: "파일 복사"
copyFolderId: "폴더 복사"
copyFileId: "파일 ID 복사"
copyFolderId: "폴더 ID 복사"
copyProfileUrl: "프로필 URL 복사"
searchUser: "사용자 검색"
reply: "답글"
@@ -108,7 +108,7 @@ renote: "리노트"
unrenote: "리노트 취소"
renoted: "리노트했습니다"
cantRenote: "이 게시물은 리노트 할 수 없습니다."
cantReRenote: "리노트를 리노트 할 수 없습니다."
cantReRenote: "리노트를 리노트할 수 없습니다."
quote: "인용"
inChannelRenote: "채널 내 리노트"
inChannelQuote: "채널 내 인용"
@@ -116,7 +116,7 @@ pinnedNote: "고정해놓은 노트"
pinned: "프로필에 고정"
you: "당신"
clickToShow: "클릭하여 보기"
sensitive: "열람주의"
sensitive: "열람 주의"
add: "추가"
reaction: "리액션"
reactions: "리액션"
@@ -156,12 +156,12 @@ addEmoji: "이모지 추가"
settingGuide: "추천 설정"
cacheRemoteFiles: "리모트 파일을 캐시"
cacheRemoteFilesDescription: "이 설정을 해지하면 리모트 파일을 캐시하지 않고 해당 파일을 직접 링크하게 됩니다. 그에 따라 서버의 저장 공간을 절약할 수 있지만, 썸네일이 생성되지 않기 때문에 통신량이 증가합니다."
cacheRemoteSensitiveFiles: "민감한 원격 파일을 캐시"
cacheRemoteSensitiveFilesDescription: "이 설정을 비활성화하면 중요한 원격 파일은 캐싱 없이 원격 인스턴스에서 직접 로드됩니다."
cacheRemoteSensitiveFiles: "리모트의 민감한 파일을 캐시"
cacheRemoteSensitiveFilesDescription: "이 설정을 비활성화하면 리모트의 민감한 파일은 캐시하지 않고 리모트에서 직접 가져오도록 합니다."
flagAsBot: "나는 봇입니다"
flagAsBotDescription: "이 계정을 자동화된 수단으로 운용할 경우에 활성화해 주세요. 이 플래그를 활성화하면, 다른 봇이 이를 참고하여 봇 끼리의 무한 연쇄 반응을 회피하거나, 이 계정의 시스템 상에서의 취급이 Bot 운영에 최적화되는 등의 변화가 생깁니다."
flagAsCat: "나는 고양이다냥"
flagAsCatDescription: "이 계정이 고양이라면 활성화 해주세요."
flagAsCatDescription: "이 계정이 고양이라면 활성화해 주세요."
flagShowTimelineReplies: "타임라인에 노트의 답글을 표시하기"
flagShowTimelineRepliesDescription: "이 설정을 활성화하면 타임라인에 다른 유저 간의 답글을 표시합니다."
autoAcceptFollowed: "팔로우 중인 유저로부터의 팔로우 요청을 자동 수락"
@@ -207,7 +207,7 @@ instanceInfo: "서버 정보"
statistics: "통계"
clearQueue: "대기열 비우기"
clearQueueConfirmTitle: "대기열을 비우시겠습니까?"
clearQueueConfirmText: "대기열에 남아 있는 노트는 더이상 연합되지 않습니다. 보통의 경우 이 작업은 필요하지 않습니다."
clearQueueConfirmText: "대기열에 남아 있는 노트는 더 이상 연합되지 않습니다. 보통의 경우 이 작업은 필요하지 않습니다."
clearCachedFiles: "캐시 비우기"
clearCachedFilesConfirm: "캐시된 리모트 파일을 모두 삭제하시겠습니까?"
blockedInstances: "차단된 서버"
@@ -1075,7 +1075,7 @@ enableServerMachineStats: "서버의 머신 사양을 공개하기"
enableIdenticonGeneration: "유저마다의 Identicon 생성 유효화"
turnOffToImprovePerformance: "이 기능을 끄면 성능이 향상될 수 있습니다."
createInviteCode: "초대 코드 생성"
createWithOptions: "설정으로 생성"
createWithOptions: "옵션을 지정하여 생성"
createCount: "초대 수"
inviteCodeCreated: "초대 코드 생성됨"
inviteLimitExceeded: "초대 코드 생성 한도를 초과했습니다."
@@ -1087,7 +1087,7 @@ inviteCodeUsedAt: "다음에 사용된 초대 코드"
registeredUserUsingInviteCode: "초대 코드 사용 대상"
waitingForMailAuth: "이메일 인증 보류 중"
inviteCodeCreator: "초대 코드 생성자"
usedAt: "사용 기간"
usedAt: "사용 시각"
unused: "사용되지 않음"
used: "사용됨"
expired: "만료됨"
@@ -1402,7 +1402,7 @@ _role:
canPublicNote: "공개 노트 허용"
canInvite: "서버 초대 코드 발행"
inviteLimit: "초대 한도"
inviteLimitCycle: "초대 발급 재사용 대기 시간"
inviteLimitCycle: "초대 발급 간"
inviteExpirationTime: "초대 만료 기간"
canManageCustomEmojis: "커스텀 이모지 관리"
driveCapacity: "드라이브 용량"
@@ -1996,7 +1996,7 @@ _deck:
introduction: "칼럼을 조합해서 나만의 인터페이스를 구성해 보아요!"
introduction2: "나중에라도 화면 우측의 + 버튼을 눌러 새 칼럼을 추가할 수 있습니다."
widgetsIntroduction: "칼럼 메뉴의 \"위젯 편집\"에서 위젯을 추가해 주세요"
useSimpleUiForNonRootPages: "탐색 페이지에 간단한 UI 사용하기"
useSimpleUiForNonRootPages: "루트 이외의 페이지로 접속한 경우 UI 간략화하기"
_columns:
main: "메인"
widgets: "위젯"

File diff suppressed because it is too large Load Diff

View File

@@ -49,6 +49,7 @@ delete: "Удалить"
deleteAndEdit: "Удалить и отредактировать"
deleteAndEditConfirm: "Удалить эту заметку и создать отредактированную? Все реакции, ссылки и ответы на существующую будут будут потеряны."
addToList: "Добавить в список"
addToAntenna: "Добавить к антенне"
sendMessage: "Отправить сообщение"
copyRSS: "Скопировать RSS"
copyUsername: "Скопировать имя пользователя"
@@ -155,6 +156,8 @@ addEmoji: "Добавить эмодзи"
settingGuide: "Рекомендуемые настройки"
cacheRemoteFiles: "Кешировать внешние файлы"
cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы."
cacheRemoteSensitiveFiles: "Кешировать внешние файлы"
cacheRemoteSensitiveFilesDescription: "Описание удаленных внешних файлов в кэше"
flagAsBot: "Аккаунт бота"
flagAsBotDescription: "Включите, если этот аккаунт управляется программой. Это позволит системе Misskey учитывать это, а также поможет разработчикам других ботов предотвратить бесконечные циклы взаимодействия."
flagAsCat: "Аккаунт кота"
@@ -316,6 +319,7 @@ copyUrl: "Копировать ссылку"
rename: "Переименовать"
avatar: "Аватар"
banner: "Шапка"
displayOfSensitiveMedia: "Определение деликатного контента"
whenServerDisconnected: "Когда соединение с сервером потеряно"
disconnectedFromServer: "Разорвано соединение с сервером"
reload: "Перезагрузить"
@@ -840,6 +844,8 @@ breakFollow: "Отписка"
breakFollowConfirm: "Удалить из подписок пользователя ?"
itsOn: "Включено"
itsOff: "Выключено"
on: "Вкл"
off: "Выкл"
emailRequiredForSignup: "Для регистрации учётной записи нужен адрес электронной почты"
unread: "Непрочитанное"
filter: "Фильтры"
@@ -994,6 +1000,7 @@ cannotBeChangedLater: "Это нельзя изменить позже"
reactionAcceptance: "Принятие реакций"
likeOnly: "Только лайки"
likeOnlyForRemote: "Только лайки с удалённых серверов"
nonSensitiveOnly: "Безопасный серфинг"
rolesAssignedToMe: "Мои роли"
resetPasswordConfirm: "Сбросить пароль?"
sensitiveWords: "Чувствительные слова"
@@ -1014,7 +1021,15 @@ noteIdOrUrl: "ID или ссылка на заметку"
video: "Видео"
videos: "Видео"
dataSaver: "Экономия трафика"
accountMigration: "Перенести учётную запись"
accountMoved: "Учетная запись перенесена"
operationForbidden: "Эта операция невозможна."
addMemo: "Добавить заметку"
editMemo: "Редактировать заметку"
reactionsList: "Реакции"
renotesList: "Репосты"
notificationDisplay: "Отображение уведомления"
leftTop: "Верхний левый угол"
horizontal: "Сбоку"
youFollowing: "Подписки"
options: "Настройки ролей"

View File

@@ -389,10 +389,13 @@ help: "Hjälp"
close: "Stäng"
invites: "Inbjudan"
members: "Medlemmar"
transfer: "Överför"
text: "Text"
enable: "Aktivera"
next: "Nästa"
invitations: "Inbjudan"
invitationCode: "Inbjudningskod"
available: "Tillgängligt"
weakPassword: "Svagt Lösenord"
normalPassword: "Medel Lösenord"
strongPassword: "Starkt Lösenord"
@@ -481,6 +484,7 @@ windowMinimize: "Minimera"
windowRestore: "Återställ"
pleaseDonate: "Misskey är en gratis programvara som används på {host}. Donera gärna för att göra utvecklingen ständigt, tack!"
resetPasswordConfirm: "Återställ verkligen ditt lösenord?"
dataSaver: "Databesparing"
_achievements:
_types:
_open3windows:

View File

@@ -1,7 +1,7 @@
---
_lang_: "ภาษาไทย"
headlineMisskey: "เชื่อมต่อเครือข่ายโดยโน้ต"
introMisskey: "ยินดีต้อนรับจ้าาา! Misskey เป็นบริการไมโครบล็อกโอเพ่นซอร์ส แบบการกระจายอำนาจ\nสร้าง \"โน้ต\" เพื่อแบ่งปันความคิดของคุณกับทุกคนรอบตัวคุณกันเถอะ 📡\nด้วยการ \"รีแอคชั่นผู้คน\" คุณยังสามารถแสดงความรู้สึกของคุณเกี่ยวกับบันทึกของทุกคนได้อย่างรวดเร็ว 👍\n\nแล้วมาท่องสำรวจโลกใบใหม่กันเถอะ! 🚀"
headlineMisskey: "เชื่อมต่อระบบ Network ด้วย Note"
introMisskey: "ยินดีต้อนรับทุกคนจ้า! Misskey คือ บริการไมโครบล็อกกิ้ง (MicroBlogging) แบบกระจายศูนย์อำนาจ (Decentralized) \n\nเขียน \"โน้ต (Note)\" เพื่อส่งต่อเรื่องราวของคุณให้ทั้งโลกได้รับรู้📡\nและอย่าลืมที่จะ \"React\" กับเรื่องราวของคนอื่น ๆ ด้วย! 👍\n\nมุ่งสู่โลกใบใหม่กันเถอะ🚀"
poweredByMisskeyDescription: "{name} เป็นส่วนหนึ่งในบริการที่ถูกขับเคลื่อนโดยแพลตฟอร์มโอเพ่นซอร์ส <b>Misskey</b> (เรียกว่า \"อินสแตนซ์ Misskey\")"
monthAndDay: "{month}/{day}"
search: "ค้นหา"
@@ -98,7 +98,7 @@ enterListName: "ใส่ชื่อสำหรับรายการลิ
privacy: "ความเป็นส่วนตัว"
makeFollowManuallyApprove: "ติดตามคำขอที่ต้องได้รับการอนุมัติ"
defaultNoteVisibility: "การมองเห็นที่เป็นค่าเริ่มต้น"
follow: "กำลังติดตาม"
follow: "ติดตาม"
followRequest: "ส่งคำขอติดตาม"
followRequests: "ส่งคำขอติดตาม"
unfollow: "เลิกติดตาม"
@@ -183,7 +183,7 @@ selectUser: "เลือกผู้ใช้งาน"
recipient: "ผู้รับ"
annotation: "ความคิดเห็น"
federation: "เฟดิเวิร์ส"
instances: "ตัวอย่าง"
instances: "Server"
registeredAt: "จดทะเบียนที่"
latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว"
latestStatus: "สถานะล่าสุด"
@@ -339,7 +339,7 @@ thisYear: "ปีนี้"
thisMonth: "เดือนนี้"
today: "วันนี้"
dayX: "{day}"
monthX: "{เดือน}"
monthX: "เดือน {month}"
yearX: "{year}"
pages: "หน้า"
integration: "รวบรวม"
@@ -1091,6 +1091,9 @@ usedAt: "ใช้แล้วที่"
unused: "ไม่ใช้แล้ว"
used: "ใช้แล้ว"
expired: "หมดอายุแล้ว"
doYouAgree: "ยอมรับมั้ย?"
beSureToReadThisAsItIsImportant: "กรุณาอ่านข้อมูลที่สำคัญอันนี้"
iHaveReadXCarefullyAndAgree: "ฉันได้อ่านข้อความ \"{x}\" และยินยอม"
_initialAccountSetting:
accountCreated: "คุณได้สร้างบัญชีของคุณสำเร็จเรียบร้อยแล้ว!"
letsStartAccountSetup: "สำหรับผู้เริ่มต้นมาตั้งค่าโปรไฟล์ของคุณกันเถอะ"
@@ -1996,6 +1999,7 @@ _deck:
introduction: "สร้างอินเทอร์เฟซที่สมบูรณ์แบบสำหรับคุณโดยจัดเรียงคอลัมน์ได้อย่างอิสระ!"
introduction2: "คลิกที่เครื่องหมาย + ทางขวาของหน้าจอเพื่อเพิ่มคอลัมน์ใหม่ทุกครั้งที่คุณต้องการ"
widgetsIntroduction: "กรุณาเลือก \"แก้ไขวิดเจ็ต\" ในเมนูคอลัมน์และเพิ่มวิดเจ็ต"
useSimpleUiForNonRootPages: "แสดง UI ของ Root Page อย่างง่าย "
_columns:
main: "หลัก"
widgets: "วิดเจ็ต"

634
locales/uz-UZ.yml Normal file
View File

@@ -0,0 +1,634 @@
---
_lang_: "O'zbek tili "
headlineMisskey: "Qaydlar bilan bog'langan tarmoq"
introMisskey: "Xush kelibsiz! Misskey ochiq kodli, markazlashmagan mikroblogging xizmati.\nO'zingizni fikrlaringizni atrofingizdagilar bilan ulashish uchun \"Qaydlar\" yarating. 📡\nUstiga-ustak, \"Reaktsiyalar\" yordamida siz boshqalarning xatlari haqidagi o'zingizni xissiyotlaringizni tez ravishda bildiring. 👍\nQani, yangi dunyoni kashf qilaylik! 🚀"
poweredByMisskeyDescription: "{name} ochiq manbali <b>Misskey</b>(\"Misskey instance\" deb ataladi) platformasi tomonidan qurilgan servislardan biri. "
monthAndDay: "{day}/{month}"
search: "Izlash"
notifications: "Xabarnomalar"
username: "Foydalanuvchi nomi"
password: "Parol"
forgotPassword: "Parolni unutib qo'ydim"
fetchingAsApObject: "Fediversedan olib kelinmoqda..."
ok: "Ho'p"
gotIt: "Tushunarli!"
cancel: "Bekor qilish"
noThankYou: "Hozir emas"
enterUsername: "Foydalanuvchini nomini kiriting"
renotedBy: "{user} tomonidan qayta qayd etildi"
noNotes: "Qaydlar mavjud emas"
noNotifications: "Xabarnomalar mavjud emas"
instance: "Server"
settings: "Sozalamalar"
notificationSettings: "Xabarnomalar sozlamalari"
basicSettings: "Asosiy sozlamalar"
otherSettings: "Qoshimcha sozlamalar"
openInWindow: "Yangi oynada ochish"
profile: "Profil"
timeline: "Xronologiya"
noAccountDescription: "Ushbu foydalanuvchi hali o'zi haqida ma'lumot yozmagan."
login: "Kirish"
loggingIn: "Kirilmoqda"
logout: "Chiqish"
signup: "Ro'yxatdan o'tish"
uploading: "Yuklanmoqda..."
save: "Saqlash"
users: "Foydalanuvchilar"
addUser: "Foydalanuvchi qo'shish"
favorite: "Sevimli"
favorites: "Sevimli"
unfavorite: "Sevimlidan chiqarish"
favorited: "sevimli"
alreadyFavorited: "allaqachon sevimlilar orasida"
cantFavorite: "sevimlilarga qo'shib bo'lmadi"
pin: "Profilga qadab qo'yish"
unpin: "Profildan olib tashlash"
copyContent: "Tarkibini nusxalash"
copyLink: "Havolani nuxalash"
delete: "O'chirib tashlash"
deleteAndEdit: "O'chirish va tahrirlash"
deleteAndEditConfirm: "O'chirib, tahrirlamoqchiligingizga ishonchingiz komilmi? Siz bu qaydga tegishli barcha reaktsiyalar, qayta qaydlar va javoblarni yo'qotasiz."
addToList: "Royxatga qoshish"
addToAntenna: "Antennaga qo'shish"
sendMessage: "Xabar yuborish"
copyRSS: "RSS'ni nusxalash"
copyUsername: "Foydalanuvchi nomini nusxalash"
copyUserId: "Foydalanuvchi IDsini nusxalash"
copyNoteId: "Qayd IDsini ko'chirish"
copyFileId: "Fayl ID raqamini nusxalash"
copyFolderId: "Jild ID raqamini nusxalash"
copyProfileUrl: "Profil manzilini nusxalash"
searchUser: "Foydalanuvchini izlash"
reply: "Javob berish"
loadMore: "Koproq korish"
showMore: "Koproq korish"
showLess: "Yopish"
youGotNewFollower: "sizga obuna bo'ldi"
receiveFollowRequest: "Obuna bo'lishga ruxsat qabul qilindi"
followRequestAccepted: "Obuna bo'lishga ruxsat berildi"
mention: "Murojat"
mentions: "Eslatib o'tish"
directNotes: "Bevosita qaydlar"
importAndExport: "Import/eksport"
import: "Import"
export: "Eksport"
files: "Fayllar"
download: "Yuklab olish"
driveFileDeleteConfirm: "\"{name}\" o'chirib tashlamoqchimisiz? Buni ishlatadihan kontentni hammasidan o'chadi"
unfollowConfirm: "{name}ga obunani bekor qilmoqchimisiz?"
exportRequested: "Eksport so'raldi. Bu ozgina vaqt olishi mumkin. Tugatilgandan so'ng sizning Diskingizga qo'shiladi"
importRequested: "Import so'raldi. Bu ozgina vaqt olishi mumkin."
lists: "Ro'yxatlar"
noLists: "Hech qanday ro'yxatlar mavjud emas"
note: "Qayd"
notes: "Qaydlar"
following: "Obuna bolish"
followers: "Obunachilar"
followsYou: "Sizning obunachingiz."
createList: "Ro'yxat yaratish"
manageLists: "Ro'yxatlarni boshqarish."
error: "Xato"
somethingHappened: "Xatolik yuz berdi"
retry: "Qayta urinib ko'rish"
pageLoadError: "Sahifani yuklayotganda xatolik yuz berdi"
pageLoadErrorDescription: "Buni odatda tarmoq muammolari yoki browser keshi keltirib chiqaradi. Keshni tozalab, keyinroq urinib ko'ring"
serverIsDead: "Server javob bermayabdi. Iltimos kuting va keyinroq urinib ko'ring"
youShouldUpgradeClient: "Iltimos, ushbu sahifani ko'rish uchun sahifani yangilang."
enterListName: "Ro'yxatga nom kiriting"
privacy: "Maxfiylik"
makeFollowManuallyApprove: "Yopiq akkaunt"
follow: "Obuna bolish"
followRequest: "Obuna bo'lish uchun ruxsat olish"
followRequests: "Obuna bo'lmoqchilar"
unfollow: "obunani bker qilish"
followRequestPending: "obuna bo'lishga ruxsat kutilmoqda"
enterEmoji: "Emojini kiriting"
renote: "Qayta qaydetish"
unrenote: "Qayta qayd etishni bekor qilish"
renoted: "Qayta qaydetildi"
cantRenote: "Qyta qayd etish mumkin emas"
quote: "Iqtibos keltirish"
inChannelRenote: "Faqat kanalga qayta qayd etish"
inChannelQuote: "Kanaldagi eslatmalar"
pinnedNote: "Qadanlgan qayd"
pinned: "Profilga qadab qo'yish"
you: "Siz"
clickToShow: "Ko'rsatish uchun bosing"
sensitive: "Sezuvchan"
add: "Qo'shish"
reaction: "Reaktsiyalar"
reactions: "Reaktsiyalar"
reactionSetting: "Reaksiyalar ro'yxatingiz "
rememberNoteVisibility: "Qaydning ko'rinish sozlamarini eslab qolish"
attachCancel: "Qo'shimchani olib tashlash"
enterFileName: "Fayl nomini kiriting"
mute: "Ovozni ochirish"
unmute: "Ovozni yoqish"
renoteMute: "Qayta qaydlarni ovozini o'chirish"
renoteUnmute: "Qayta qaydlarni ovozini yoqish"
block: "Bloklash"
unblock: "Blokdan chiqarish"
suspend: "To'xtatish"
unsuspend: "Blokdan chiqarish"
blockConfirm: "Haqiqatdan ham quyidagi hisobni bloklashni xohlaysizmi? "
unblockConfirm: "Haqiqatdan ham quyidagi hisobni blokdan chiqarishni xohlaysizmi? "
suspendConfirm: "Bu hisobni toxtatib qoymoqchi ekanligingizga ishonchingiz komilmi?"
unsuspendConfirm: "Tasdiqlashni to'xtatib turish"
selectList: "Ro'yxat tanlash"
editList: "Roʻyxatni tahrirlash"
selectChannel: "Kanalni tanlang"
selectAntenna: "Antennani tanlang"
editAntenna: "Antennani tahrirlang"
selectWidget: "Vidjet tanlash"
editWidgets: "Vidjetni tahrirlash"
editWidgetsExit: "Tugadi"
customEmojis: "Maxsus emoji"
emoji: "Emoji"
emojis: "Emoji"
emojiName: "Emoji nomi"
emojiUrl: "Emoji URL'i"
addEmoji: "Emoji qo'shish"
settingGuide: "Tavsiya qilingan sozlamalar"
cacheRemoteFiles: "Tashqi fayllarni keshlash"
cacheRemoteFilesDescription: "Ushbu sozlama o'chirilgan bo'lsa tashqi fayllar bevosita tashqi serverdan yuklanadi. Buni o'chirish ombor ishlatilishini kamaytiradi, lekin traffikni ko'paytiradi, chunki eskizlar generatsiya qilinmaydi."
cacheRemoteSensitiveFiles: "Tashqi fayllarni keshlash"
flagAsBot: "Ushbu akkauntni bot sifatida belgilash"
flagAsBotDescription: "Agar bu akkaunt bot tomonidan boshqaralayotgan bo'ls, bu sozlamani yoqing. Sozlama yoqilganda, boshqa foydalanuvchilar uchun belgi sifatida ishlaydi, va Misskey ichki tizimlari bu akkauntni bot ekanini biladi."
flagAsCat: "Bu akkauntni mushuk sifatida belgilash"
flagAsCatDescription: "Ushbu akkauntni mushuk sifatida belgilash uchun ushbu sozlamani yoqing."
flagShowTimelineReplies: "Javbolarni xronogoliya bo'yicha ko'rsatish"
addAccount: "Akkaunt qo'shish"
reloadAccountsList: "Hisoblar ro'yxatini yangilash"
loginFailed: "Tizimga kirishda xatolik yuz berdi"
showOnRemote: "Masofaviy boshqaruvni ko'rish"
general: "Asosiy"
wallpaper: "Fon rasmi"
setWallpaper: "Fon rasmini o'rnatish"
removeWallpaper: "Fon rasmini olib tashlash"
searchWith: "Izlash: {q}"
youHaveNoLists: "Sizda hech qanday ro'yxatlar mavjud emas"
followConfirm: "{name} ga obuna bo'lmoqchimisiz?"
proxyAccount: "Proksi hisob"
host: "Host"
selectUser: "Foydalanuvchini tanlang"
recipient: "Qabul qiluvchi"
annotation: "Izohlar"
federation: "Federatsiya"
instances: "Serverlar"
registeredAt: "Ro'yhatdan o'tgan"
latestStatus: "So'nggi holat"
storageUsage: "Ishlatilgan xotira"
charts: "Diagrammalar"
perHour: "Soatbay"
perDay: "Kunbay"
stopActivityDelivery: "Faollikni jo'natishi to'xtatish"
blockThisInstance: "Ko;rsatilgan serverni bloklash"
operations: "Amallar"
software: "Dastur"
version: "Versiya"
metadata: "Meta ma'lumot"
withNFiles: "{n} ta fayl(lar)"
monitor: "Kuzatish"
jobQueue: "Vazifalar navbati"
cpuAndMemory: "CPU va Xotira"
network: "Tarmoq"
disk: "Disk"
instanceInfo: "Instans haqida ma'lumot"
statistics: "Statistika"
clearQueue: "Navbatni tozalash"
clearQueueConfirmTitle: "Navbatni tozalamoqchimisiz?"
clearCachedFiles: "Keshni tozalash"
blockedInstances: "Bloklangan serverlar"
muteAndBlock: "Ovozsiz va Bloklangan"
mutedUsers: "Ovozsiz foydalanuvchilar"
blockedUsers: "Bloklangan foydalanuvchilar"
noUsers: "Foydalanuvchilar yoq"
editProfile: "Profilni o'zgartirish"
noteDeleteConfirm: "Haqiqatan ham bu qaydni oʻchirib tashlamoqchimisiz?"
pinLimitExceeded: "Siz boshqa qaydlarni mahkamlay olmaysiz"
intro: "Misskeyni o'rnatish tugallandi! Iltimos, administrator foydalanuvchi yarating."
done: "Bajarildi"
processing: "Amaliyotda"
preview: "Ko'rish"
default: "Odatiy"
defaultValueIs: "Sukut bo'yicha: {value}"
noCustomEmojis: "Emojilar mavjud emas"
noJobs: "Vazifalar yo'q"
blocked: "Bloklangan"
suspended: "To'xtatilgan"
all: "Barcha"
subscribing: "Obuna bo'lish"
publishing: "Yuborilmoqda"
notResponding: "Javob bermayapti"
changePassword: "Parolni ozgartirish"
security: "Xavfsizlik"
retypedNotMatch: "Maydonlar mos kelmayapti"
currentPassword: "Joriy parol"
newPassword: "Yangi parol"
newPasswordRetype: "Yangi parolni boshqatdan tering"
attachFile: "Fayl biriktirish"
more: "Ko'proq!"
noSuchUser: "Foydalanuvchi topilmadi"
lookup: "So'rov"
announcements: "Bildirishnomalar"
imageUrl: "Rasm URL"
remove: "O'chirib tashlash"
removed: "Muvaffaqiyatli o'chirildi"
removeAreYouSure: "“{x}”ni olib tashlamoqchi ekanligingizga ishonchingiz komilmi?"
deleteAreYouSure: "“{x}”ni chindan ham yo'q qilmoqchimisiz?"
resetAreYouSure: "Haqiqatan ham qayta tiklansinmi?"
saved: "Saqlandi"
messaging: "Suhbat"
upload: "Yuklash"
keepOriginalUploading: "Asl rasmni saqlang"
uploadFromUrlDescription: "Yuklamoqchi bo'lgan faylingizga havola"
explore: "Ko'rib chiqish"
messageRead: "Oqildi"
startMessaging: "Yangi suhbatni boshlash"
nUsersRead: "{n} tomonidan o'qildi"
agreeTo: "Men {0} ga roziman"
agree: "Rozi bo'lish"
agreeBelow: "Men quyidagilarga roziman"
basicNotesBeforeCreateAccount: "Muhim qaydlar"
termsOfService: "Foydalanish shartlari"
start: "Boshlash"
home: "Bosh sahifa"
activity: "Faollik"
images: "Rasmlar"
image: "Rasm"
birthday: "Tug'ilgan kun"
yearsOld: "{age} yashar"
registeredDate: "Ro'yxatdan o'tgan sanasi"
location: "Manzil"
theme: "Rang sxemasi"
themeForLightMode: "Yorug' rejim uchun rang sxemasi"
themeForDarkMode: "Qorong'i rejim uchun rang sxemasi"
light: "Yorug'"
dark: "Qorongʻi"
lightThemes: "Yorug rang sxemasi"
darkThemes: "Qorong'i rang sxemasi"
drive: "Disk"
fileName: "Fayl nomi"
selectFile: "Faylni tanlang"
selectFiles: "Fayllarni tanlang"
selectFolder: "Jildni tanlang"
selectFolders: "Jildlarni tanlang"
renameFile: "Faylni nomini tahrirlash"
folderName: "Jild nomi"
createFolder: "Papka qo'shish"
renameFolder: "Papka nomini ozgartirish"
deleteFolder: "Papkani ochirish"
addFile: "Fayl qoshish"
emptyDrive: "Diskingiz bo'sh"
emptyFolder: "Ushbu papka bo'sh"
unableToDelete: "O'chirilmadi"
inputNewFileName: "Yangi fayl nomini kiriting"
inputNewFolderName: "Yangi papka nomini kiriting"
copyUrl: "Bog'lamadan nusxa olish"
rename: "Qayta nomlash"
avatar: "Avatar"
banner: "Banner"
disconnectedFromServer: "Server bilan ulanish uzulib qoldi"
reload: "Yangilash"
doNothing: "E'tiborsiz qoldirish"
reloadConfirm: "Timeline'ni yangilashni xohlaysizmi?"
watch: "Kuzatmoq"
unwatch: "Kuzatishni to'xtatish"
accept: "Ruxsat"
reject: "Rad etish"
normal: "Yaxshi"
instanceName: "Server nomi"
instanceDescription: "Server tavsifi"
maintainerName: "Qo'llab-quvvatlovchi"
maintainerEmail: "Administratorning elektron pochtasi"
tosUrl: "Foydalanish shartlariga havola"
thisYear: "Joriy yil"
thisMonth: "Shu oy"
today: "Bugun"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Sahifalar"
integration: "Integratsiya"
connectService: "Ulash"
disconnectService: "Uzish"
enableLocalTimeline: "Mahalliy vaqt mintaqasini yoqing"
enableGlobalTimeline: "Global vaqt mintaqasini yoqing"
registration: "Ro'yxatdan o'tish"
enableRegistration: "Ro'yxatdan o'tishni yoqing"
invite: "Taklif qilish"
inMb: "Megabaytlarda"
basicInfo: "Asosiy ma'lumot"
pinnedUsers: "Qadalgan foydalanuvchilar"
pinnedPages: "Qadalgan Sahifalar"
pinnedNotes: "Qadanlgan qayd"
hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptchani yoqish"
hcaptchaSiteKey: "Sayt kaliti"
hcaptchaSecretKey: "Mahfiy kalit"
recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHA ni yoqish"
recaptchaSiteKey: "Sayt kaliti"
recaptchaSecretKey: "Maxfiy kalit"
turnstileSiteKey: "Sayt kaliti"
turnstileSecretKey: "Maxfiy kalit"
antennas: "Antennalar"
manageAntennas: "Antennalarni boshqarish"
name: "Ism"
antennaSource: "Antenna manbai"
silence: "Sukunat"
exploreUsersCount: "{count} ta foydalanuvchi bor"
popularTags: "Ommabop teglar"
userList: "Ro'yxatlar"
about: "Haqida"
aboutMisskey: "Misskey haqida"
administrator: "Administrator"
token: "Tasdiqlash"
2fa: "Ikki faktorli autentifikatsiya"
totp: "Autentifikatsiya ilovasi"
totpDescription: "Bir martalik parollarni kiritish uchun autentifikatsiya ilovasidan foydalaning"
moderator: "Moderator"
resetPassword: "Parolni tiklash"
share: "Yuborish"
notFound: "Topilmadi"
uploadFolder: "Jildni yuklash"
cacheClear: "Keshni tozalash"
help: "Yordam"
inputMessageHere: "Xabar kiriting"
close: "Yopish"
invites: "Taklif qilish"
members: "A'zolar"
title: "Sarlavha"
text: "Matn"
enable: "Yoqish"
invitations: "Taklif qilish"
dashboard: "Boshqaruv paneli"
local: "Mahalliy"
total: "Jami"
weekOverWeekChanges: "Oxirgi haftadagi o'zgarishlar"
dayOverDayChanges: "Kecha bo'lgan o'zgarishlar"
appearance: "Tasgqi ko'rinish"
clientSettings: "Klient sozlamalari"
accountSettings: "Profil sozlamalari"
sounds: "Tovushlar"
sound: "ovoz"
none: "Hechnima"
volume: "Ovoz balandligi"
details: "Batafsil"
output: "Chiqish"
menu: "Menyu"
divider: "Ajratrmoq"
addItem: "Element qo'shish"
themeEditor: "Rang sxemasi muharriri"
enableAll: "Yoqish"
edit: "Tahrirlash"
email: "Email"
smtpHost: "Host"
smtpUser: "Foydalanuvchi nomi"
smtpPass: "Parol"
copy: "Nusxa olish"
notificationSetting: "Bildirishnoma sozlamalari"
other: "Qoshimcha"
behavior: "Hatti-harakatlar"
sample: "Namuna"
public: "Ommaviy"
clearCache: "Keshni tozalash"
onlineUsersCount: "Faol userlar"
myTheme: "Mening rang sxemam"
backgroundColor: "Fon"
accentColor: "Urg'u"
textColor: "Matn"
info: "Haqida"
user: "Foydalanuvchilar"
global: "Global"
squareAvatars: "Kvadrat avatarkalar"
searchByGoogle: "Izlash"
indefinitely: "Hech qachon"
file: "Fayllar"
label: "Yorliq"
color: "Rang"
_achievements:
_types:
_viewInstanceChart:
title: "Tahlilchi"
_ad:
hide: "Boshqa ko'rsatilmasin"
_email:
_follow:
title: "sizga obuna bo'ldi"
_registry:
key: "Kalit"
keys: "Kalit"
_instanceTicker:
none: "Boshqa ko'rsatilmasin"
always: "Doimo ko'rsatilsin"
_theme:
install: "Rang sxemasini o'rnatish"
manage: "Rang sxemalarini boshqarish"
code: "Rang sxemasining kodi"
description: "Tavsif"
installed: "{name} o'rnatildi"
installedThemes: "O'rnatilgan rang sxemalari"
alreadyInstalled: "Ushbu rang sxemasi allaqachon o'rnatilgan"
invalid: "Ushbu rang sxemasining formati yaroqsiz"
make: "Rang sxemasini yasash"
base: "Asos"
addConstant: "O'zgarmas qo'shish"
constant: "O'zgarmas"
color: "Rang"
key: "Kalit"
func: "Funksiyalar"
funcKind: "Funksiya turi"
argument: "Argument"
darken: "Qoraytirish"
lighten: "Yoritish"
inputConstantName: "Ushbu o'zgarmas uchun nom kiriting"
deleteConstantConfirm: "Siz rostdan ham {const} o'zgarmasni o'chirmoqchimisiz?"
keys:
accent: "Urg'u"
bg: "Fon"
fg: "Matn"
focus: "Fokus"
panel: "Panel"
shadow: "Soya"
header: "Sarlavha"
navBg: "Yon panel foni"
navFg: "Yon panel matni"
mention: "Murojat"
renote: "Qayta qaydetish"
divider: "Ajratrmoq"
accentDarken: "Urg'u (Qoraytirilgan)"
accentLighten: "Urg'u (Yoritilgan)"
fgHighlighted: "Belgilangan matn"
_sfx:
note: "Qaydlar"
notification: "Xabarnomalar"
chat: "Suhbat"
_2fa:
renewTOTPCancel: "Hozir emas"
_permissions:
"read:blocks": "Bloklangan foydalanuvchilar roʻyxatini koʻring"
"write:blocks": "Bloklangan foydalanuvchilar roʻyxatini tahrirlang"
_weekday:
saturday: "Shanba"
_widgets:
profile: "Profil"
instanceInfo: "Instans haqida ma'lumot"
notifications: "Xabarnomalar"
timeline: "Xronologiya"
clock: "Soat"
activity: "Faollik"
photos: "Rasmlar"
digitalClock: "Raqamli soat"
unixClock: "UNIX soat"
federation: "Federatsiya"
button: "Tugma"
jobQueue: "Vazifalar navbati"
_userList:
chooseList: "Ro'yxat tanlash"
_cw:
show: "Koproq korish"
chars: "{count} ta belgi(lar)"
files: "{count} ta fayl(lar)"
_poll:
noOnlyOneChoice: "Kamida ikkita tanvol kerak"
infinite: "Hech qachon"
at: "...da tugatish"
after: "...dan keyin tugatish"
deadlineTime: "Vaqt"
duration: "Davomiylik"
votesCount: "{n} ovozlar"
totalVotes: "Umuman {n} ovozlar"
vote: "Ovoz berish"
showResult: "Natijalarni ko'rish"
voted: "Ovoz berildi"
closed: "Yakunladi"
remainingDays: "{d} kun {h} soat qoldi"
remainingHours: "{h} soat {m} daqiqa qoldi"
remainingMinutes: "{m} daqiqa {s} sekund qoldi"
remainingSeconds: "{s} sekund qoldi"
_visibility:
public: "Ommaviy"
publicDescription: "Sizning ovozingiz barcha foydalanuvchilarga ko'rinadi"
home: "Bosh sahifa"
followers: "Obunachilar"
specified: "Bevosita"
_profile:
name: "Ism"
username: "Foydalanuvchi nomi"
description: "Biografiya"
metadata: "Qo'shimcha ma'lumot"
metadataLabel: "Yorliq"
metadataContent: "Tarkib"
changeBanner: "Bannerni o'zgartirish"
_exportOrImport:
allNotes: "Barcha qaydlar"
followingList: "Obuna bolish"
muteList: "Ovozni ochirish"
blockingList: "Bloklangan foydalanuvchilar"
userLists: "Ro'yxatlar"
_charts:
federation: "Federatsiya"
apRequest: "So'rovlar"
usersTotal: "Foydalanuvchilarning umumiy soni"
activeUsers: "Faol foydalanuvchilar"
notesTotal: "Qaydlarning umumiy soni"
filesTotal: "Fayllarning umumiy soni"
_instanceCharts:
requests: "So'rovlar"
notes: "Qaydlar sonidagi farq"
cacheSize: "Kesh hajmidagi farq"
files: "Fayllar sonidagi farq"
_timelines:
home: "Bosh sahifa"
local: "Mahalliy"
social: "Ijtimoiy"
global: "Global"
_play:
featured: "Mashhur"
title: "Sarlavha"
script: "Skript"
summary: "Tavsif"
_pages:
newPage: "Yangi Sahifa yaratish"
editPage: "Ushbu Sahifani tahrirlash"
created: "Sahifa muvaffaqiyatli yaratildi"
updated: "Sahifa muvaffaqiyatli tahrirlandi"
deleted: "Sahifa muvaffaqiyatli o'chirildi"
pageSetting: "Sahifa sozlamalari"
nameAlreadyExists: "Ko'rsatilgan Sahifa URL'i allaqachon mavjud"
invalidNameTitle: "Ko'rsatilgan Sahifa URL'i yaroqsiz"
editThisPage: "Ushbu Sahifani tahrirlash"
viewPage: "Sizning Sahifalaringizni ko'rish"
my: "Mening Sahifalarim"
featured: "Mashhur"
contents: "Tarkib"
title: "Sarlavha"
url: "Sahifa URL'i"
summary: "Sahifa bayoni"
font: "Shrift"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
selectType: "Turni tanlang"
contentBlocks: "Tarkib"
blocks:
text: "Matn"
textarea: "Matn maydoni"
section: "Bo'lim"
image: "Rasmlar"
button: "Tugma"
note: "Biriktirilgan qayd"
_note:
id: "Qayd ID"
detailed: "Batafsil ko'rinishi"
_relayStatus:
requesting: "Kutilmoqda"
accepted: "Tasdiqlandi"
rejected: "Rad etildi"
_notification:
fileUploaded: "Fayl muvaffaqiyatli yuklandi"
youGotMention: "{name} sizni eslab o'tdi"
youGotReply: "{name} sizga javob berdi"
youGotQuote: "{name} sizdan iqtibos keltirdi"
youRenoted: "{name} dan qayta qayd qilish"
youWereFollowed: "sizga obuna bo'ldi"
unreadAntennaNote: "Antenna {name}"
_types:
all: "Barchasi"
follow: "Obuna bolish"
mention: "Murojat"
renote: "Qayta qaydetish"
quote: "Iqtibos keltirish"
reaction: "Reaktsiyalar"
receiveFollowRequest: "Qabul qilingan kuzatuv so'rovlari"
_actions:
reply: "Javob berish"
renote: "Qayta qayd qilish"
_deck:
alwaysShowMainColumn: "Har doim asosiy ustunni ko'rsatish"
columnAlign: "Ustunlarni tekislash"
addColumn: "Ustun qo'shish"
configureColumn: "Ustun sozlamalari"
swapLeft: "Chapdagi ustun bilan joyni almashtirish"
swapRight: "O'ngdagi ustun bilan joyni almashtirish"
swapUp: "Yuqoridagi ustun bilan joyni almashtirish"
swapDown: "Quyidagi ustun bilan joyni almashtirish"
profile: "Profil"
newProfile: "Yangi profil"
deleteProfile: "Profilni ochirib tashlash"
_columns:
main: "Asosiy"
notifications: "Xabarnomalar"
tl: "Xronologiya"
antenna: "Antennalar"
list: "Royxat"
channel: "Kanal"
mentions: "Eslatib o'tish"
direct: "Bevosita qaydlar"
roleTimeline: "Rol xronologiyasi"
_webhookSettings:
name: "Ism"
active: "Yoqilgan"
_events:
renote: "Qayta qayd qilinganda"
mention: "Eslanganda"

View File

@@ -1091,6 +1091,9 @@ usedAt: "使用时间"
unused: "未使用"
used: "已使用"
expired: "已过期"
doYouAgree: "你同意吗?"
beSureToReadThisAsItIsImportant: "请好好阅读,这真的很重要。"
iHaveReadXCarefullyAndAgree: "我已经仔细阅读并同意了「{x}」的内容。"
_initialAccountSetting:
accountCreated: "账户创建完成了!"
letsStartAccountSetup: "来进行帐户的初始设置吧。"

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
{
"name": "misskey",
"version": "13.14.0-beta.6",
"version": "13.14.1",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
},
"packageManager": "pnpm@8.6.0",
"packageManager": "pnpm@8.6.9",
"workspaces": [
"packages/frontend",
"packages/backend",

View File

@@ -2,14 +2,7 @@ import Redis from 'ioredis';
import { loadConfig } from './built/config.js';
const config = loadConfig();
const redis = new Redis({
port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
const redis = new Redis(config.redis);
redis.on('connect', () => redis.disconnect());
redis.on('error', (e) => {

View File

@@ -79,7 +79,6 @@
"ajv": "8.12.0",
"archiver": "5.3.1",
"async-mutex": "^0.4.0",
"autwh": "0.1.0",
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"bullmq": "4.4.0",
@@ -93,7 +92,6 @@
"content-disposition": "0.5.4",
"date-fns": "2.30.0",
"deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1",
"fastify": "4.20.0",
"feed": "4.2.2",
"file-type": "18.5.0",
@@ -139,7 +137,6 @@
"rename": "1.0.4",
"rss-parser": "3.13.0",
"rxjs": "7.8.1",
"s-age": "1.1.2",
"sanitize-html": "2.11.0",
"semver": "7.5.4",
"sharp": "0.32.3",
@@ -157,7 +154,6 @@
"typeorm": "0.3.17",
"typescript": "5.1.6",
"ulid": "2.3.0",
"unzipper": "0.10.14",
"vary": "1.1.2",
"web-push": "3.6.3",
"ws": "8.13.0",
@@ -172,7 +168,6 @@
"@types/cbor": "6.0.0",
"@types/color-convert": "2.0.0",
"@types/content-disposition": "0.5.5",
"@types/escape-regexp": "0.0.1",
"@types/fluent-ffmpeg": "2.1.21",
"@types/jest": "29.5.3",
"@types/js-yaml": "4.0.5",
@@ -191,7 +186,6 @@
"@types/qrcode": "1.5.1",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.4",
"@types/redis": "4.0.11",
"@types/rename": "1.0.4",
"@types/sanitize-html": "2.9.0",
"@types/semver": "7.5.0",
@@ -199,10 +193,8 @@
"@types/sinonjs__fake-timers": "8.1.2",
"@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3",
"@types/unzipper": "0.10.6",
"@types/vary": "1.1.0",
"@types/web-push": "3.3.2",
"@types/websocket": "1.0.5",
"@types/ws": "8.5.5",
"@typescript-eslint/eslint-plugin": "5.61.0",
"@typescript-eslint/parser": "5.61.0",

View File

@@ -41,14 +41,7 @@ const $meilisearch: Provider = {
const $redis: Provider = {
provide: DI.redis,
useFactory: (config: Config) => {
return new Redis.Redis({
port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
return new Redis.Redis(config.redis);
},
inject: [DI.config],
};
@@ -56,14 +49,7 @@ const $redis: Provider = {
const $redisForPub: Provider = {
provide: DI.redisForPub,
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
password: config.redisForPubsub.pass,
keyPrefix: `${config.redisForPubsub.prefix}:`,
db: config.redisForPubsub.db ?? 0,
});
const redis = new Redis.Redis(config.redisForPubsub);
return redis;
},
inject: [DI.config],
@@ -72,14 +58,7 @@ const $redisForPub: Provider = {
const $redisForSub: Provider = {
provide: DI.redisForSub,
useFactory: (config: Config) => {
const redis = new Redis.Redis({
port: config.redisForPubsub.port,
host: config.redisForPubsub.host,
family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
password: config.redisForPubsub.pass,
keyPrefix: `${config.redisForPubsub.prefix}:`,
db: config.redisForPubsub.db ?? 0,
});
const redis = new Redis.Redis(config.redisForPubsub);
redis.subscribe(config.host);
return redis;
},

View File

@@ -6,6 +6,16 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
import type { RedisOptions } from 'ioredis';
type RedisOptionsSource = Partial<RedisOptions> & {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
/**
* ユーザーが設定する必要のある情報
@@ -35,30 +45,9 @@ export type Source = {
user: string;
pass: string;
}[];
redis: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
redisForPubsub?: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
redisForJobQueue?: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
redis: RedisOptionsSource;
redisForPubsub?: RedisOptionsSource;
redisForJobQueue?: RedisOptionsSource;
meilisearch?: {
host: string;
port: string;
@@ -119,8 +108,9 @@ export type Mixin = {
mediaProxy: string;
externalMediaProxyEnabled: boolean;
videoThumbnailGenerator: string | null;
redisForPubsub: NonNullable<Source['redisForPubsub']>;
redisForJobQueue: NonNullable<Source['redisForJobQueue']>;
redis: RedisOptions & RedisOptionsSource;
redisForPubsub: RedisOptions & RedisOptionsSource;
redisForJobQueue: RedisOptions & RedisOptionsSource;
};
export type Config = Source & Mixin;
@@ -182,9 +172,9 @@ export function loadConfig() {
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
: null;
if (!config.redis.prefix) config.redis.prefix = mixin.host;
if (config.redisForPubsub == null) config.redisForPubsub = config.redis;
if (config.redisForJobQueue == null) config.redisForJobQueue = config.redis;
mixin.redis = convertRedisOptions(config.redis, mixin.host);
mixin.redisForPubsub = config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, mixin.host) : mixin.redis;
mixin.redisForJobQueue = config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, mixin.host) : mixin.redis;
return Object.assign(config, mixin);
}
@@ -196,3 +186,14 @@ function tryCreateUrl(url: string) {
throw new Error(`url="${url}" is not a valid URL.`);
}
}
function convertRedisOptions(options: RedisOptionsSource, host: string): RedisOptions & RedisOptionsSource {
return {
...options,
password: options.pass,
prefix: options.prefix ?? host,
family: options.family == null ? 0 : options.family,
keyPrefix: `${options.prefix ?? host}:`,
db: options.db ?? 0,
};
}

View File

@@ -4,10 +4,9 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
import type { User } from '@/models/entities/User.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';

View File

@@ -33,7 +33,7 @@ export class CreateSystemUserService {
// Generate secret
const secret = generateNativeUserToken();
const keyPair = await genRsaKeyPair(4096);
const keyPair = await genRsaKeyPair();
let account!: User;

View File

@@ -2,6 +2,7 @@ import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom';
import tinycolor from 'tinycolor2';
import * as Redis from 'ioredis';
import type { Instance } from '@/models/entities/Instance.js';
import type Logger from '@/logger.js';
import { DI } from '@/di-symbols.js';
@@ -10,7 +11,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import type { DOMWindow } from 'jsdom';
import * as Redis from 'ioredis';
type NodeInfo = {
openRegistrations?: unknown;

View File

@@ -574,7 +574,7 @@ export class NoteCreateService implements OnApplicationShutdown {
where: {
userId: data.reply.userId,
threadId: data.reply.threadId ?? data.reply.id,
}
},
});
if (!isThreadMuted) {

View File

@@ -108,7 +108,7 @@ export class QueueService {
removeOnFail: true,
};
await this.deliverQueue.addBulk(Array.from(inboxes.entries()).map(d => ({
await this.deliverQueue.addBulk(Array.from(inboxes.entries(), d => ({
name: d[0],
data: {
user,

View File

@@ -8,8 +8,9 @@ import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { Config } from '@/config.js';
import type Logger from '@/logger.js';
import { UtilityService } from '@/core/UtilityService.js';
import { WebfingerService } from '@/core/WebfingerService.js';
import { ILink, WebfingerService } from '@/core/WebfingerService.js';
import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { bindThis } from '@/decorators.js';
@@ -27,6 +28,7 @@ export class RemoteUserResolveService {
private utilityService: UtilityService,
private webfingerService: WebfingerService,
private remoteLoggerService: RemoteLoggerService,
private apDbResolverService: ApDbResolverService,
private apPersonService: ApPersonService,
) {
this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user');
@@ -67,6 +69,22 @@ export class RemoteUserResolveService {
if (user == null) {
const self = await this.resolveSelf(acctLower);
if (self.href.startsWith(this.config.url)) {
const local = this.apDbResolverService.parseUri(self.href);
if (local.local && local.type === 'users') {
// the LR points to local
return (await this.apDbResolverService
.getUserFromApId(self.href)
.then((u) => {
if (u == null) {
throw new Error('local user not found');
} else {
return u;
}
})) as LocalUser;
}
}
this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
return await this.apPersonService.createPerson(self.href);
}
@@ -119,7 +137,7 @@ export class RemoteUserResolveService {
}
@bindThis
private async resolveSelf(acctLower: string) {
private async resolveSelf(acctLower: string): Promise<ILink> {
this.logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
const finger = await this.webfingerService.webfinger(acctLower).catch(err => {
this.logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ err.statusCode ?? err.message }`);

View File

@@ -220,14 +220,19 @@ export class RoleService implements OnApplicationShutdown {
}
@bindThis
public async getUserRoles(userId: User['id']) {
public async getUserAssigns(userId: User['id']) {
const now = Date.now();
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
// 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId);
return assigns;
}
@bindThis
public async getUserRoles(userId: User['id']) {
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
const assigns = await this.getUserAssigns(userId);
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
return [...assignedRoles, ...matchedCondRoles];

View File

@@ -92,7 +92,7 @@ export class SignupService {
const keyPair = await new Promise<string[]>((res, rej) =>
generateKeyPair('rsa', {
modulusLength: 4096,
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',

View File

@@ -1,5 +1,6 @@
import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { IsNull } from 'typeorm';
import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { QueueService } from '@/core/QueueService.js';
@@ -21,9 +22,8 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js';
import Logger from '../logger.js';
import { IsNull } from 'typeorm';
import { AccountMoveService } from '@/core/AccountMoveService.js';
import Logger from '../logger.js';
const logger = new Logger('following/create');
@@ -322,7 +322,7 @@ export class UserFollowingService implements OnModuleInit {
where: {
followerId: follower.id,
followeeId: followee.id,
}
},
});
if (following === null || !following.follower || !following.followee) {
@@ -412,8 +412,8 @@ export class UserFollowingService implements OnModuleInit {
followerId: user.id,
followee: {
movedToUri: IsNull(),
}
}
},
},
});
const nonMovedFollowers = await this.followingsRepository.count({
relations: {
@@ -423,8 +423,8 @@ export class UserFollowingService implements OnModuleInit {
followeeId: user.id,
follower: {
movedToUri: IsNull(),
}
}
},
},
});
await this.usersRepository.update(
{ id: user.id },
@@ -646,7 +646,7 @@ export class UserFollowingService implements OnModuleInit {
where: {
followeeId: followee.id,
followerId: follower.id,
}
},
});
if (!following || !following.followee || !following.follower) return;

View File

@@ -52,7 +52,7 @@ export class VideoProcessingService {
query({
thumbnail: '1',
url,
})
}),
);
}
}

View File

@@ -6,12 +6,12 @@ import { query as urlQuery } from '@/misc/prelude/url.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js';
type ILink = {
export type ILink = {
href: string;
rel?: string;
};
type IWebFinger = {
export type IWebFinger = {
links: ILink[];
subject: string;
};

View File

@@ -95,7 +95,7 @@ export class ApAudienceService {
private isPublic(id: string): boolean {
return [
'https://www.w3.org/ns/activitystreams#Public',
'as#Public',
'as:Public',
'Public',
].includes(id);
}

View File

@@ -220,6 +220,23 @@ export class ApPersonService implements OnModuleInit {
return null;
}
private async resolveAvatarAndBanner(user: RemoteUser, icon: any, image: any): Promise<Pick<RemoteUser, 'avatarId' | 'bannerId' | 'avatarUrl' | 'bannerUrl' | 'avatarBlurhash' | 'bannerBlurhash'>> {
const [avatar, banner] = await Promise.all([icon, image].map(img => {
if (img == null) return null;
if (user == null) throw new Error('failed to create user: user is null');
return this.apImageService.resolveImage(user, img).catch(() => null);
}));
return {
avatarId: avatar?.id ?? null,
bannerId: banner?.id ?? null,
avatarUrl: avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
avatarBlurhash: avatar?.blurhash ?? null,
bannerBlurhash: banner?.blurhash ?? null,
};
}
/**
* Personを作成します。
*/
@@ -259,6 +276,16 @@ export class ApPersonService implements OnModuleInit {
// Create user
let user: RemoteUser | null = null;
//#region カスタム絵文字取得
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host)
.then(_emojis => _emojis.map(emoji => emoji.name))
.catch(err => {
this.logger.error(`error occured while fetching user emojis`, { stack: err });
return [];
});
//#endregion
try {
// Start transaction
await this.db.transaction(async transactionalEntityManager => {
@@ -285,6 +312,7 @@ export class ApPersonService implements OnModuleInit {
tags,
isBot,
isCat: (person as any).isCat === true,
emojis,
})) as RemoteUser;
await transactionalEntityManager.save(new UserProfile({
@@ -321,6 +349,9 @@ export class ApPersonService implements OnModuleInit {
if (user == null) throw new Error('failed to create user: user is null');
// Register to the cache
this.cacheService.uriPersonCache.set(user.uri, user);
// Register host
this.federatedInstanceService.fetch(host).then(async i => {
this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
@@ -336,45 +367,16 @@ export class ApPersonService implements OnModuleInit {
this.hashtagService.updateUsertags(user, tags);
//#region アバターとヘッダー画像をフェッチ
const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => {
if (img == null) return null;
if (user == null) throw new Error('failed to create user: user is null');
return this.apImageService.resolveImage(user, img).catch(() => null);
}));
try {
const updates = await this.resolveAvatarAndBanner(user, person.icon, person.image);
await this.usersRepository.update(user.id, updates);
user = { ...user, ...updates };
const avatarId = avatar?.id ?? null;
const bannerId = banner?.id ?? null;
const avatarUrl = avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null;
const bannerUrl = banner ? this.driveFileEntityService.getPublicUrl(banner) : null;
const avatarBlurhash = avatar?.blurhash ?? null;
const bannerBlurhash = banner?.blurhash ?? null;
await this.usersRepository.update(user.id, {
avatarId,
bannerId,
avatarUrl,
bannerUrl,
avatarBlurhash,
bannerBlurhash,
});
user.avatarId = avatarId;
user.bannerId = bannerId;
user.avatarUrl = avatarUrl;
user.bannerUrl = bannerUrl;
user.avatarBlurhash = avatarBlurhash;
user.bannerBlurhash = bannerBlurhash;
//#endregion
//#region カスタム絵文字取得
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], host).catch(err => {
this.logger.info(`extractEmojis: ${err}`);
return [];
});
const emojiNames = emojis.map(emoji => emoji.name);
await this.usersRepository.update(user.id, { emojis: emojiNames });
// Register to the cache
this.cacheService.uriPersonCache.set(user.uri, user);
} catch (err) {
this.logger.error('error occured while fetching user avatar/banner', { stack: err });
}
//#endregion
await this.updateFeatured(user.id, resolver).catch(err => this.logger.error(err));
@@ -400,7 +402,7 @@ export class ApPersonService implements OnModuleInit {
if (uri.startsWith(`${this.config.url}/`)) return;
//#region このサーバーに既に登録されているか
const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser | null;
const exist = await this.fetchPerson(uri) as RemoteUser | null;
if (exist === null) return;
//#endregion
@@ -413,12 +415,6 @@ export class ApPersonService implements OnModuleInit {
this.logger.info(`Updating the Person: ${person.id}`);
// アバターとヘッダー画像をフェッチ
const [avatar, banner] = await Promise.all([person.icon, person.image].map(img => {
if (img == null) return null;
return this.apImageService.resolveImage(exist, img).catch(() => null);
}));
// カスタム絵文字取得
const emojis = await this.apNoteService.extractEmojis(person.tag ?? [], exist.host).catch(e => {
this.logger.info(`extractEmojis: ${e}`);
@@ -454,6 +450,7 @@ export class ApPersonService implements OnModuleInit {
movedToUri: person.movedTo ?? null,
alsoKnownAs: person.alsoKnownAs ?? null,
isExplorable: person.discoverable,
...(await this.resolveAvatarAndBanner(exist, person.icon, person.image).catch(() => ({}))),
} as Partial<RemoteUser> & Pick<RemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
const moving = ((): boolean => {
@@ -476,18 +473,6 @@ export class ApPersonService implements OnModuleInit {
if (moving) updates.movedAt = new Date();
if (avatar) {
updates.avatarId = avatar.id;
updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
updates.avatarBlurhash = avatar.blurhash;
}
if (banner) {
updates.bannerId = banner.id;
updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
updates.bannerBlurhash = banner.blurhash;
}
// Update user
await this.usersRepository.update(exist.id, updates);

View File

@@ -3,8 +3,8 @@ import { DI } from '@/di-symbols.js';
import type { AbuseUserReportsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class AbuseUserReportEntityService {

View File

@@ -4,8 +4,8 @@ import type { AuthSessionsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { AuthSession } from '@/models/entities/AuthSession.js';
import type { User } from '@/models/entities/User.js';
import { AppEntityService } from './AppEntityService.js';
import { bindThis } from '@/decorators.js';
import { AppEntityService } from './AppEntityService.js';
@Injectable()
export class AuthSessionEntityService {

View File

@@ -50,7 +50,7 @@ export class ChannelEntityService {
const hasUnreadNote = meId ? await this.noteUnreadsRepository.exist({
where: {
noteChannelId: channel.id,
userId: meId
userId: meId,
},
}) : undefined;

View File

@@ -4,8 +4,8 @@ import type { FollowRequestsRepository } from '@/models/index.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { FollowRequest } from '@/models/entities/FollowRequest.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class FollowRequestEntityService {

View File

@@ -3,8 +3,8 @@ import { DI } from '@/di-symbols.js';
import type { GalleryLikesRepository } from '@/models/index.js';
import type { } from '@/models/entities/Blocking.js';
import type { GalleryLike } from '@/models/entities/GalleryLike.js';
import { GalleryPostEntityService } from './GalleryPostEntityService.js';
import { bindThis } from '@/decorators.js';
import { GalleryPostEntityService } from './GalleryPostEntityService.js';
@Injectable()
export class GalleryLikeEntityService {

View File

@@ -4,8 +4,8 @@ import type { ModerationLogsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { } from '@/models/entities/Blocking.js';
import type { ModerationLog } from '@/models/entities/ModerationLog.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class ModerationLogEntityService {

View File

@@ -4,8 +4,8 @@ import type { NoteFavoritesRepository } from '@/models/index.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { NoteFavorite } from '@/models/entities/NoteFavorite.js';
import { NoteEntityService } from './NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { NoteEntityService } from './NoteEntityService.js';
@Injectable()
export class NoteFavoriteEntityService {

View File

@@ -4,8 +4,8 @@ import type { PageLikesRepository } from '@/models/index.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { PageLike } from '@/models/entities/PageLike.js';
import { PageEntityService } from './PageEntityService.js';
import { bindThis } from '@/decorators.js';
import { PageEntityService } from './PageEntityService.js';
@Injectable()
export class PageLikeEntityService {

View File

@@ -3,8 +3,8 @@ import { DI } from '@/di-symbols.js';
import type { SigninsRepository } from '@/models/index.js';
import type { } from '@/models/entities/Blocking.js';
import type { Signin } from '@/models/entities/Signin.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable()
export class SigninEntityService {

View File

@@ -1,11 +1,11 @@
import { Writable, WritableOptions } from "node:stream";
import { Writable, WritableOptions } from 'node:stream';
export class DevNull extends Writable implements NodeJS.WritableStream {
constructor(opts?: WritableOptions) {
super(opts);
}
constructor(opts?: WritableOptions) {
super(opts);
}
_write (chunk: any, encoding: BufferEncoding, cb: (err?: Error | null) => void) {
setImmediate(cb);
}
_write (chunk: any, encoding: BufferEncoding, cb: (err?: Error | null) => void) {
setImmediate(cb);
}
}

View File

@@ -5,10 +5,10 @@ const CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
export const ulidRegExp = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/;
export function parseUlid(id: string): { date: Date; } {
const timestamp = id.slice(0, 10);
let time = 0;
for (let i = 0; i < 10; i++) {
time = time * 32 + CHARS.indexOf(timestamp[i]);
}
return { date: new Date(time) };
const timestamp = id.slice(0, 10);
let time = 0;
for (let i = 0; i < 10; i++) {
time = time * 32 + CHARS.indexOf(timestamp[i]);
}
return { date: new Date(time) };
}

View File

@@ -10,7 +10,7 @@ export async function awaitAll<T>(obj: Promiseable<T>): Promise<T> {
const resolvedValues = await Promise.all(values.map(value =>
(!value || !value.constructor || value.constructor.name !== 'Object')
? value
: awaitAll(value)
: awaitAll(value),
));
for (let i = 0; i < keys.length; i++) {

View File

@@ -49,7 +49,6 @@ import { User } from '@/models/entities/User.js';
import { UserIp } from '@/models/entities/UserIp.js';
import { UserKeypair } from '@/models/entities/UserKeypair.js';
import { UserList } from '@/models/entities/UserList.js';
import { UserListFavorite } from './entities/UserListFavorite.js';
import { UserListJoining } from '@/models/entities/UserListJoining.js';
import { UserNotePining } from '@/models/entities/UserNotePining.js';
import { UserPending } from '@/models/entities/UserPending.js';
@@ -64,6 +63,7 @@ import { Role } from '@/models/entities/Role.js';
import { RoleAssignment } from '@/models/entities/RoleAssignment.js';
import { Flash } from '@/models/entities/Flash.js';
import { FlashLike } from '@/models/entities/FlashLike.js';
import { UserListFavorite } from './entities/UserListFavorite.js';
import type { Repository } from 'typeorm';
export {

View File

@@ -15,11 +15,8 @@ export const QUEUE = {
export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.QueueOptions {
return {
connection: {
port: config.redisForJobQueue.port,
host: config.redisForJobQueue.host,
family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family,
password: config.redisForJobQueue.pass,
db: config.redisForJobQueue.db ?? 0,
...config.redisForJobQueue,
keyPrefix: undefined
},
prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`,
};

View File

@@ -9,10 +9,10 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { Note } from '@/models/entities/Note.js';
import { EmailService } from '@/core/EmailService.js';
import { bindThis } from '@/decorators.js';
import { SearchService } from '@/core/SearchService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
import type { DbUserDeleteJobData } from '../types.js';
import { SearchService } from "@/core/SearchService.js";
@Injectable()
export class DeleteAccountProcessorService {

View File

@@ -1,7 +1,7 @@
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
import { ZipReader } from 'slacc';
import { DataSource } from 'typeorm';
import unzipper from 'unzipper';
import { DI } from '@/di-symbols.js';
import type { EmojisRepository, DriveFilesRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
@@ -72,9 +72,9 @@ export class ImportCustomEmojisProcessorService {
}
const outputPath = path + '/emojis';
const unzipStream = fs.createReadStream(destPath);
const extractor = unzipper.Extract({ path: outputPath });
extractor.on('close', async () => {
try {
this.logger.succ(`Unzipping to ${outputPath}`);
ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath));
const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8');
const meta = JSON.parse(metaRaw);
@@ -115,8 +115,12 @@ export class ImportCustomEmojisProcessorService {
cleanup();
this.logger.succ('Imported');
});
unzipStream.pipe(extractor);
this.logger.succ(`Unzipping to ${outputPath}`);
} catch (e) {
if (e instanceof Error || typeof e === 'string') {
this.logger.error(e);
}
cleanup();
throw e;
}
}
}

View File

@@ -1,16 +1,16 @@
import { Inject, Injectable } from '@nestjs/common';
import type * as Bull from 'bullmq';
import { UserFollowingService } from '@/core/UserFollowingService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import { RelationshipJobData } from '../types.js';
import type { UsersRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
import { LocalUser, RemoteUser } from '@/models/entities/User.js';
import { RelationshipJobData } from '../types.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type * as Bull from 'bullmq';
@Injectable()
export class RelationshipProcessorService {

View File

@@ -3,6 +3,8 @@ import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
import rename from 'rename';
import sharp from 'sharp';
import { sharpBmp } from 'sharp-read-bmp';
import type { Config } from '@/config.js';
import type { DriveFile, DriveFilesRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
@@ -18,11 +20,9 @@ import { contentDisposition } from '@/misc/content-disposition.js';
import { FileInfoService } from '@/core/FileInfoService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
import { isMimeImage } from '@/misc/is-mime-image.js';
import sharp from 'sharp';
import { sharpBmp } from 'sharp-read-bmp';
import { correctFilename } from '@/misc/correct-filename.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -180,8 +180,8 @@ export class FileServerService {
reply.header('Content-Disposition',
contentDisposition(
'inline',
correctFilename(file.filename, image.ext)
)
correctFilename(file.filename, image.ext),
),
);
return image.data;
}
@@ -278,11 +278,11 @@ export class FileServerService {
};
} else {
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) }))
.resize({
height: 'emoji' in request.query ? 128 : 320,
withoutEnlargement: true,
})
.webp(webpDefault);
.resize({
height: 'emoji' in request.query ? 128 : 320,
withoutEnlargement: true,
})
.webp(webpDefault);
image = {
data,
@@ -355,8 +355,8 @@ export class FileServerService {
reply.header('Content-Disposition',
contentDisposition(
'inline',
correctFilename(file.filename, image.ext)
)
correctFilename(file.filename, image.ext),
),
);
return image.data;
} catch (e) {

View File

@@ -1,18 +1,18 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm';
import vary from 'vary';
import fastifyAccepts from '@fastify/accepts';
import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
import type { User } from '@/models/entities/User.js';
import * as Acct from '@/misc/acct.js';
import { NodeinfoServerService } from './NodeinfoServerService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import type { FindOptionsWhere } from 'typeorm';
import { bindThis } from '@/decorators.js';
import { NodeinfoServerService } from './NodeinfoServerService.js';
import type { FindOptionsWhere } from 'typeorm';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
import fastifyAccepts from '@fastify/accepts';
@Injectable()
export class WellKnownServerService {

View File

@@ -13,9 +13,9 @@ import { EmailService } from '@/core/EmailService.js';
import { LocalUser } from '@/models/entities/User.js';
import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
import { bindThis } from '@/decorators.js';
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
import { SigninService } from './SigninService.js';
import type { FastifyRequest, FastifyReply } from 'fastify';
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
@Injectable()
export class SignupApiService {

View File

@@ -4,6 +4,7 @@ import type { DriveFilesRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -55,6 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private customEmojiService: CustomEmojiService,
private emojiEntityService: EmojiEntityService,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -77,9 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
emojiId: emoji.id,
});
return {
id: emoji.id,
};
return this.emojiEntityService.packDetailed(emoji);
});
}
}

View File

@@ -91,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (queryarry) {
emojis = emojis.filter(emoji =>
queryarry.includes(`:${emoji.name}:`)
queryarry.includes(`:${emoji.name}:`),
);
} else {
emojis = emojis.filter(emoji =>

View File

@@ -60,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
query.limit(ps.limit);
query.skip(ps.offset);
query.offset(ps.offset);
const tickets = await query.getMany();

View File

@@ -61,6 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const signins = await this.signinsRepository.findBy({ userId: user.id });
const roleAssigns = await this.roleService.getUserAssigns(user.id);
const roles = await this.roleService.getUserRoles(user.id);
return {
@@ -85,6 +86,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
signins,
policies: await this.roleService.getUserPolicies(user.id),
roles: await this.roleEntityService.packMany(roles, me),
roleAssigns: roleAssigns.map(a => ({
createdAt: a.createdAt.toISOString(),
expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null,
roleId: a.roleId,
})),
};
});
}

View File

@@ -105,7 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
query.limit(ps.limit);
query.skip(ps.offset);
query.offset(ps.offset);
const users = await query.getMany();

View File

@@ -76,6 +76,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchAntenna);
}
this.antennasRepository.update(antenna.id, {
isActive: true,
lastUsedAt: new Date(),
});
const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
const noteIdsRes = await this.redisClient.xrevrange(
`antennaTimeline:${antenna.id}`,
@@ -112,11 +117,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.noteReadService.read(me.id, notes);
}
this.antennasRepository.update(antenna.id, {
isActive: true,
lastUsedAt: new Date(),
});
return await this.noteEntityService.packMany(notes, me);
});
}

View File

@@ -112,6 +112,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
withReplies: ps.withReplies,
withFile: ps.withFile,
notify: ps.notify,
isActive: true,
lastUsedAt: new Date(),
});
this.globalEventService.publishInternalEvent('antennaUpdated', await this.antennasRepository.findOneByOrFail({ id: antenna.id }));

View File

@@ -5,8 +5,8 @@ import type { UsersRepository, BlockingsRepository } from '@/models/index.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['account'],
@@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
where: {
blockerId: blocker.id,
blockeeId: blockee.id,
}
},
});
if (!exist) {

View File

@@ -2,8 +2,8 @@ import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['account', 'notes', 'clips'],

View File

@@ -126,7 +126,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
query.andWhere('instance.host like :host', { host: '%' + sqlLikeEscape(ps.host.toLowerCase()) + '%' });
}
const instances = await query.limit(ps.limit).skip(ps.offset).getMany();
const instances = await query.limit(ps.limit).offset(ps.offset).getMany();
return await this.instanceEntityService.packMany(instances);
});

View File

@@ -5,8 +5,8 @@ import type { UsersRepository, FollowingsRepository } from '@/models/index.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['following', 'users'],

View File

@@ -5,8 +5,8 @@ import type { UsersRepository, FollowingsRepository } from '@/models/index.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['following', 'users'],

View File

@@ -42,7 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.orderBy('tag.count', 'DESC')
.groupBy('tag.id')
.limit(ps.limit)
.skip(ps.offset)
.offset(ps.offset)
.getMany();
return hashtags.map(tag => tag.name);

View File

@@ -23,7 +23,7 @@ export const meta = {
id: 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a',
kind: 'permission',
},
}
},
} as const;
export const paramDef = {

View File

@@ -103,7 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const procedures = this.twoFactorAuthenticationService.getProcedures();
if (!(procedures as any)[attestation.fmt]) {
throw new Error('unsupported fmt');
throw new Error(`unsupported fmt: ${attestation.fmt}. Supported ones: ${Object.keys(procedures)}`);
}
const verificationData = (procedures as any)[attestation.fmt].verify({

View File

@@ -267,7 +267,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
super(meta, paramDef, async (ps, me) => {
const instance = await this.metaService.fetch(true);
const ads = await this.adsRepository.createQueryBuilder("ads")
const ads = await this.adsRepository.createQueryBuilder('ads')
.where('ads.expiresAt > :now', { now: new Date() })
.andWhere('ads.startsAt <= :now', { now: new Date() })
.andWhere(new Brackets(qb => {

View File

@@ -4,8 +4,8 @@ import type { NotesRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['notes'],

View File

@@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const polls = await query
.orderBy('poll.noteId', 'DESC')
.limit(ps.limit)
.skip(ps.offset)
.offset(ps.offset)
.getMany();
if (polls.length === 0) return [];

View File

@@ -3,8 +3,8 @@ import type { NotesRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['notes'],

View File

@@ -3,8 +3,8 @@ import type { PromoReadsRepository } from '@/models/index.js';
import { IdService } from '@/core/IdService.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['notes'],

View File

@@ -35,7 +35,7 @@ export const meta = {
code: 'NO_SUCH_REGISTRATION',
id: ' b09d8066-8064-5613-efb6-0e963b21d012',
},
}
},
} as const;
export const paramDef = {

View File

@@ -81,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (me) this.queryService.generateBlockQueryForUsers(query, me);
query.limit(ps.limit);
query.skip(ps.offset);
query.offset(ps.offset);
const users = await query.getMany();

View File

@@ -5,8 +5,8 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
import { ApiError } from '../../error.js';
export const meta = {
tags: ['users'],

View File

@@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
query.setParameters(followingQuery.getParameters());
const users = await query.limit(ps.limit).skip(ps.offset).getMany();
const users = await query.limit(ps.limit).offset(ps.offset).getMany();
return await this.userEntityService.packMany(users, me, { detail: true });
});

View File

@@ -75,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
users = await usernameQuery
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.skip(ps.offset)
.offset(ps.offset)
.getMany();
} else {
const nameQuery = this.usersRepository.createQueryBuilder('user')
@@ -102,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
users = await nameQuery
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.skip(ps.offset)
.offset(ps.offset)
.getMany();
if (users.length < ps.limit) {
@@ -128,7 +128,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
users = users.concat(await query
.orderBy('user.updatedAt', 'DESC', 'NULLS LAST')
.limit(ps.limit)
.skip(ps.offset)
.offset(ps.offset)
.getMany(),
);
}

View File

@@ -3,9 +3,9 @@ import { isUserRelated } from '@/misc/is-user-related.js';
import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import Channel from '../channel.js';
import { StreamMessages } from '../types.js';
import { RoleService } from '@/core/RoleService.js';
class RoleTimelineChannel extends Channel {
public readonly chName = 'roleTimeline';

View File

@@ -3,7 +3,7 @@ import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common';
import { createBullBoard } from '@bull-board/api';
import { BullAdapter } from '@bull-board/api/bullAdapter.js';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
import { FastifyAdapter } from '@bull-board/fastify';
import ms from 'ms';
import sharp from 'sharp';
@@ -168,7 +168,7 @@ export class ClientServerService {
this.dbQueue,
this.objectStorageQueue,
this.webhookDeliverQueue,
].map(q => new BullAdapter(q)),
].map(q => new BullMQAdapter(q)),
serverAdapter,
});

View File

@@ -16,8 +16,12 @@ block og
meta(property='og:title' content= title)
meta(property='og:description' content= post.description)
meta(property='og:url' content= url)
meta(property='og:image' content= post.files[0].thumbnailUrl)
meta(property='twitter:card' content='summary_large_image')
if post.isSensitive
meta(property='og:image' content= avatarUrl)
meta(property='twitter:card' content='summary')
else
meta(property='og:image' content= post.files[0].thumbnailUrl)
meta(property='twitter:card' content='summary_large_image')
block meta
if user.host || profile.noCrawle

View File

@@ -5,8 +5,8 @@ block vars
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = `${config.url}/notes/${note.id}`;
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
- const image = (note.files || []).find(file => file.type.startsWith('image/') && !file.isSensitive)
- const video = (note.files || []).find(file => file.type.startsWith('video/') && !file.isSensitive)
- const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive)
- const videos = (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive)
block title
= `${title} | ${instanceName}`
@@ -19,15 +19,17 @@ block og
meta(property='og:title' content= title)
meta(property='og:description' content= summary)
meta(property='og:url' content= url)
if video
meta(property='og:video:url' content= video.url)
meta(property='og:video:secure_url' content= video.url)
meta(property='og:video:type' content= video.type)
// FIXME: add width and height
// FIXME: add embed player for Twitter
if image
if videos.length
each video in videos
meta(property='og:video:url' content= video.url)
meta(property='og:video:secure_url' content= video.url)
meta(property='og:video:type' content= video.type)
// FIXME: add width and height
// FIXME: add embed player for Twitter
if images.length
meta(property='twitter:card' content='summary_large_image')
meta(property='og:image' content= image.url)
each image in images
meta(property='og:image' content= image.url)
else
meta(property='twitter:card' content='summary')
meta(property='og:image' content= avatarUrl)

View File

@@ -4,8 +4,9 @@
"scripts": {
"watch": "vite",
"build": "vite build",
"storybook-dev": "chokidar 'src/**/*.{mdx,ts,vue}' -d 1000 -t 1000 --initial -i '**/*.stories.ts' -c 'pkill -f node_modules/storybook/index.js; node_modules/.bin/tsc -p .storybook && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js && node_modules/.bin/storybook dev -p 6006 --ci'",
"build-storybook": "tsc -p .storybook && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js && storybook build",
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
"build-storybook-pre": "tsc -p .storybook && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
"build-storybook": "pnpm build-storybook-pre && storybook build",
"chromatic": "chromatic",
"test": "vitest --run",
"test-and-coverage": "vitest --run --coverage",
@@ -19,7 +20,7 @@
"@rollup/plugin-json": "6.0.0",
"@rollup/plugin-replace": "5.0.2",
"@rollup/pluginutils": "5.0.2",
"@syuilo/aiscript": "0.13.3",
"@syuilo/aiscript": "0.15.0",
"@tabler/icons-webfont": "2.25.0",
"@vitejs/plugin-vue": "4.2.3",
"@vue-macros/reactivity-transform": "0.3.15",
@@ -76,24 +77,24 @@
"vuedraggable": "next"
},
"devDependencies": {
"@storybook/addon-actions": "7.1.0",
"@storybook/addon-essentials": "7.1.0",
"@storybook/addon-interactions": "7.1.0",
"@storybook/addon-links": "7.1.0",
"@storybook/addon-storysource": "7.1.0",
"@storybook/addons": "7.1.0",
"@storybook/blocks": "7.1.0",
"@storybook/core-events": "7.1.0",
"@storybook/addon-actions": "7.0.27",
"@storybook/addon-essentials": "7.0.27",
"@storybook/addon-interactions": "7.0.27",
"@storybook/addon-links": "7.0.27",
"@storybook/addon-storysource": "7.0.27",
"@storybook/addons": "7.0.27",
"@storybook/blocks": "7.0.27",
"@storybook/core-events": "7.0.27",
"@storybook/jest": "0.1.0",
"@storybook/manager-api": "7.1.0",
"@storybook/preview-api": "7.1.0",
"@storybook/react": "7.1.0",
"@storybook/react-vite": "7.1.0",
"@storybook/manager-api": "7.0.27",
"@storybook/preview-api": "7.0.27",
"@storybook/react": "7.0.27",
"@storybook/react-vite": "7.0.27",
"@storybook/testing-library": "0.2.0",
"@storybook/theming": "7.1.0",
"@storybook/types": "7.1.0",
"@storybook/vue3": "7.1.0",
"@storybook/vue3-vite": "7.1.0",
"@storybook/theming": "7.0.27",
"@storybook/types": "7.0.27",
"@storybook/vue3": "7.0.27",
"@storybook/vue3-vite": "7.0.27",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/vue": "7.0.0",
"@types/escape-regexp": "0.0.1",
@@ -116,7 +117,6 @@
"@vitest/coverage-v8": "0.33.0",
"@vue/runtime-core": "3.3.4",
"acorn": "8.10.0",
"chokidar-cli": "3.0.0",
"cross-env": "7.0.3",
"cypress": "12.17.1",
"eslint": "8.45.0",
@@ -127,11 +127,12 @@
"micromatch": "4.0.5",
"msw": "1.2.2",
"msw-storybook-addon": "1.8.0",
"nodemon": "3.0.1",
"prettier": "3.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"start-server-and-test": "2.0.0",
"storybook": "7.1.0",
"storybook": "7.0.27",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly",
"vite-plugin-turbosnap": "1.0.2",

View File

@@ -19,7 +19,7 @@
</div>
<div v-if="file.isSensitive" :class="[$style.label, $style.red]">
<img :class="$style.labelImg" src="/client-assets/label-red.svg"/>
<p :class="$style.labelText">NSFW</p>
<p :class="$style.labelText">{{ i18n.ts.sensitive }}</p>
</div>
<MkDriveFileThumbnail :class="$style.thumbnail" :file="file" fit="contain"/>

View File

@@ -108,8 +108,7 @@ function waitForDecode() {
.then(() => {
loaded = true;
}, error => {
console.error('Error occurred during decoding image', img.value, error);
throw Error(error);
console.log('Error occurred during decoding image', img.value, error);
});
} else {
loaded = false;

View File

@@ -20,7 +20,7 @@
<template v-if="hide">
<div :class="$style.hiddenText">
<div :class="$style.hiddenTextWrapper">
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div>
@@ -30,7 +30,7 @@
<div :class="$style.indicators">
<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
<div v-if="image.comment" :class="$style.indicator">ALT</div>
<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);">NSFW</div>
<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
</div>
<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;"></i></button>
<i class="ti ti-eye-off" :class="$style.hide" @click.stop="hide = true"></i>

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div ref="root">
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/>
<div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container">
<div
@@ -23,7 +23,7 @@
</template>
<script lang="ts" setup>
import { onMounted, watch, shallowRef } from 'vue';
import { onMounted, shallowRef } from 'vue';
import * as misskey from 'misskey-js';
import PhotoSwipeLightbox from 'photoswipe/lightbox';
import PhotoSwipe from 'photoswipe';
@@ -34,19 +34,26 @@ import XVideo from '@/components/MkMediaVideo.vue';
import * as os from '@/os';
import { FILE_TYPE_BROWSERSAFE } from '@/const';
import { defaultStore } from '@/store';
import { getScrollContainer, getBodyScrollHeight } from '@/scripts/scroll';
const props = defineProps<{
mediaList: misskey.entities.DriveFile[];
raw?: boolean;
}>();
const root = shallowRef<HTMLDivElement>();
const container = shallowRef<HTMLElement | null | undefined>(undefined);
const gallery = shallowRef<HTMLDivElement>();
const pswpZIndex = os.claimZIndex('middle');
document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
const count = $computed(() => props.mediaList.filter(media => previewable(media)).length);
/**
* アスペクト比をmediaListWithOneImageAppearanceに基づいていい感じに調整する
* aspect-ratioではなくheightを使う
*/
function calcAspectRatio() {
if (!gallery.value) return;
if (!gallery.value || !root.value) return;
let img = props.mediaList[0];
@@ -55,28 +62,46 @@ function calcAspectRatio() {
return;
}
// アスペクト比上限設定では、横長の場合は高さを縮小させる
const ratioMax = (ratio: number) => `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`;
const width = gallery.value.clientWidth;
const heightMin = (ratio: number) => {
const imgResizeRatio = width / img.properties.width;
const imgDrawHeight = img.properties.height * imgResizeRatio;
const maxHeight = width * ratio;
const height = Math.min(imgDrawHeight, maxHeight);
if (_DEV_) console.log('Image height calculated:', { width, properties: img.properties, imgResizeRatio, imgDrawHeight, maxHeight, height });
return `${height}px`;
};
switch (defaultStore.state.mediaListWithOneImageAppearance) {
case '16_9':
gallery.value.style.aspectRatio = ratioMax(16 / 9);
gallery.value.style.height = heightMin(9 / 16);
break;
case '1_1':
gallery.value.style.aspectRatio = ratioMax(1);
gallery.value.style.height = heightMin(1);
break;
case '2_3':
gallery.value.style.aspectRatio = ratioMax(2 / 3);
gallery.value.style.height = heightMin(3 / 2);
break;
default:
gallery.value.style.aspectRatio = '';
default: {
if (!container.value) container.value = getScrollContainer(root.value);
const maxHeight = Math.max(64, (container.value ? container.value.clientHeight : getBodyScrollHeight()) * 0.5 || 360);
if (width === 0 || !maxHeight) return;
const imgResizeRatio = width / img.properties.width;
const imgDrawHeight = img.properties.height * imgResizeRatio;
gallery.value.style.height = `${Math.max(64, Math.min(imgDrawHeight, maxHeight))}px`;
gallery.value.style.minHeight = 'initial';
gallery.value.style.maxHeight = 'initial';
break;
}
}
gallery.value.style.aspectRatio = 'initial';
}
watch([defaultStore.reactiveState.mediaListWithOneImageAppearance, gallery], () => calcAspectRatio());
onMounted(() => {
calcAspectRatio();
const lightbox = new PhotoSwipeLightbox({
dataSource: props.mediaList
.filter(media => {
@@ -203,7 +228,7 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
&.n1 {
grid-template-rows: 1fr;
// default (expand)
// default but fallback (expand)
min-height: 64px;
max-height: clamp(
64px,
@@ -212,20 +237,20 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
);
&.n116_9 {
min-height: none;
max-height: none;
min-height: initial;
max-height: initial;
aspect-ratio: 16 / 9; // fallback
}
&.n11_1{
min-height: none;
max-height: none;
min-height: initial;
max-height: initial;
aspect-ratio: 1 / 1; // fallback
}
&.n12_3 {
min-height: none;
max-height: none;
min-height: initial;
max-height: initial;
aspect-ratio: 2 / 3; // fallback
}
}

View File

@@ -431,9 +431,15 @@ defineExpose({
margin: auto;
padding: 32px;
display: flex;
// TODO: mask-imageはiOSだとやたら重い。なんとかしたい
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
overflow: auto;
@media (max-width: 500px) {
padding: 16px;
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
}
}
}

View File

@@ -165,6 +165,7 @@ import { getNoteSummary } from '@/scripts/get-note-summary';
import { MenuItem } from '@/types/menu';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { showMovedDialog } from '@/scripts/show-moved-dialog';
import { shouldCollapsed } from '@/scripts/collapsed';
const props = defineProps<{
note: misskey.entities.Note;
@@ -204,17 +205,7 @@ let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note
const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
const isLong = (appearNote.cw == null && appearNote.text != null && (
(appearNote.text.includes('$[x2')) ||
(appearNote.text.includes('$[x3')) ||
(appearNote.text.includes('$[x4')) ||
(appearNote.text.includes('$[scale')) ||
(appearNote.text.includes('$[position')) ||
(appearNote.text.split('\n').length > 9) ||
(appearNote.text.length > 500) ||
(appearNote.files.length >= 5) ||
(urls && urls.length >= 4)
));
const isLong = shouldCollapsed(appearNote);
const collapsed = ref(appearNote.cw == null && isLong);
const isDeleted = ref(false);
const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));

View File

@@ -540,7 +540,7 @@ function onCompositionEnd(ev: CompositionEvent) {
}
async function onPaste(ev: ClipboardEvent) {
for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({ item, i }))) {
for (const { item, i } of Array.from(ev.clipboardData.items, (item, i) => ({ item, i }))) {
if (item.kind === 'file') {
const file = item.getAsFile();
const lio = file.name.lastIndexOf('.');
@@ -907,6 +907,7 @@ defineExpose({
display: flex;
flex-wrap: nowrap;
gap: 4px;
margin-bottom: -10px;
}
.headerLeft {
@@ -1024,7 +1025,7 @@ defineExpose({
}
.targetNote {
padding: 0 20px 16px 20px;
padding: 10px 20px 16px 20px;
}
.withQuote {

View File

@@ -5,7 +5,7 @@
<div :class="$style.file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
<MkDriveFileThumbnail :data-id="element.id" :class="$style.thumbnail" :file="element" fit="cover"/>
<div v-if="element.isSensitive" :class="$style.sensitive">
<i class="ti ti-alert-triangle" style="margin: auto;"></i>
<i class="ti ti-eye-exclamation" style="margin: auto;"></i>
</div>
</div>
</template>

View File

@@ -9,7 +9,10 @@
<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
</div>
<div style="text-align: center;">{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}</div>
<div style="text-align: center;">
<div>{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}</div>
<div style="font-weight: bold; margin-top: 0.5em;">{{ i18n.ts.beSureToReadThisAsItIsImportant }}</div>
</div>
<MkFolder v-if="availableServerRules" :defaultOpen="true">
<template #label>{{ i18n.ts.serverRules }}</template>
@@ -19,7 +22,7 @@
<li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
</ol>
<MkSwitch v-model="agreeServerRules" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch>
<MkSwitch :modelValue="agreeServerRules" style="margin-top: 16px;" @update:modelValue="updateAgreeServerRules">{{ i18n.ts.agree }}</MkSwitch>
</MkFolder>
<MkFolder v-if="availableTos" :defaultOpen="true">
@@ -28,7 +31,7 @@
<a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a>
<MkSwitch v-model="agreeTos" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch>
<MkSwitch :modelValue="agreeTos" style="margin-top: 16px;" @update:modelValue="updateAgreeTos">{{ i18n.ts.agree }}</MkSwitch>
</MkFolder>
<MkFolder :defaultOpen="true">
@@ -37,7 +40,7 @@
<a href="https://misskey-hub.net/docs/notes.html" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a>
<MkSwitch v-model="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree>{{ i18n.ts.agree }}</MkSwitch>
<MkSwitch :modelValue="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree @update:modelValue="updateAgreeNote">{{ i18n.ts.agree }}</MkSwitch>
</MkFolder>
<div v-if="!agreed" style="text-align: center;">{{ i18n.ts.pleaseAgreeAllToContinue }}</div>
@@ -52,13 +55,14 @@
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { instance } from '@/instance';
import { i18n } from '@/i18n';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os';
const availableServerRules = instance.serverRules.length > 0;
const availableTos = instance.tosUrl != null;
@@ -75,6 +79,48 @@ const emit = defineEmits<{
(ev: 'cancel'): void;
(ev: 'done'): void;
}>();
async function updateAgreeServerRules(v: boolean) {
if (v) {
const confirm = await os.confirm({
type: 'question',
title: i18n.ts.doYouAgree,
text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.serverRules }),
});
if (confirm.canceled) return;
agreeServerRules.value = true;
} else {
agreeServerRules.value = false;
}
}
async function updateAgreeTos(v: boolean) {
if (v) {
const confirm = await os.confirm({
type: 'question',
title: i18n.ts.doYouAgree,
text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.termsOfService }),
});
if (confirm.canceled) return;
agreeTos.value = true;
} else {
agreeTos.value = false;
}
}
async function updateAgreeNote(v: boolean) {
if (v) {
const confirm = await os.confirm({
type: 'question',
title: i18n.ts.doYouAgree,
text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.basicNotesBeforeCreateAccount }),
});
if (confirm.canceled) return;
agreeNote.value = true;
} else {
agreeNote.value = false;
}
}
</script>
<style lang="scss" module>

View File

@@ -31,16 +31,13 @@ import MkMediaList from '@/components/MkMediaList.vue';
import MkPoll from '@/components/MkPoll.vue';
import { i18n } from '@/i18n';
import { $i } from '@/account';
import { shouldCollapsed } from '@/scripts/collapsed';
const props = defineProps<{
note: misskey.entities.Note;
}>();
const isLong =
props.note.cw == null && props.note.text != null && (
(props.note.text.split('\n').length > 9) ||
(props.note.text.length > 500)
);
const isLong = shouldCollapsed(props.note);
const collapsed = $ref(isLong);
</script>

View File

@@ -16,8 +16,8 @@ import tinycolor from 'tinycolor2';
const loaded = !!window.TagCanvas;
const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
const computedStyle = getComputedStyle(document.documentElement);
const idForCanvas = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
let available = $ref(false);
let rootEl = $shallowRef<HTMLElement | null>(null);
let canvasEl = $shallowRef<HTMLCanvasElement | null>(null);

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